blob: 9a695149ae36b041fd9033be1508b2189457c33b [file] [log] [blame]
/*
* Copyright (C) 2015 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 "WebViewImpl.h"
#if PLATFORM(MAC)
#import "ColorSpaceData.h"
#import "GenericCallback.h"
#import "Logging.h"
#import "NativeWebGestureEvent.h"
#import "NativeWebKeyboardEvent.h"
#import "NativeWebMouseEvent.h"
#import "NativeWebWheelEvent.h"
#import "PageClient.h"
#import "PasteboardTypes.h"
#import "StringUtilities.h"
#import "ViewGestureController.h"
#import "WKFullScreenWindowController.h"
#import "WKImmediateActionController.h"
#import "WKTextInputWindowController.h"
#import "WKViewLayoutStrategy.h"
#import "WKWebView.h"
#import "WebEditCommandProxy.h"
#import "WebPageProxy.h"
#import "WebProcessProxy.h"
#import "_WKThumbnailViewInternal.h"
#import <HIToolbox/CarbonEventsCore.h>
#import <WebCore/AXObjectCache.h>
#import <WebCore/CoreGraphicsSPI.h>
#import <WebCore/DataDetectorsSPI.h>
#import <WebCore/DictionaryLookup.h>
#import <WebCore/DragData.h>
#import <WebCore/KeypressCommand.h>
#import <WebCore/LookupSPI.h>
#import <WebCore/NSImmediateActionGestureRecognizerSPI.h>
#import <WebCore/NSWindowSPI.h>
#import <WebCore/PlatformEventFactoryMac.h>
#import <WebCore/SoftLinking.h>
#import <WebCore/ViewState.h>
#import <WebCore/WebActionDisablingCALayerDelegate.h>
#import <WebCore/WebCoreCALayerExtras.h>
#import <WebCore/WebCoreFullScreenPlaceholderView.h>
#import <WebCore/WebCoreFullScreenWindow.h>
#import <WebKitSystemInterface.h>
SOFT_LINK_CONSTANT_MAY_FAIL(Lookup, LUNotificationPopoverWillClose, NSString *)
@interface WKWindowVisibilityObserver : NSObject {
NSView *_view;
WebKit::WebViewImpl *_impl;
}
- (instancetype)initWithView:(NSView *)view impl:(WebKit::WebViewImpl&)impl;
- (void)startObserving:(NSWindow *)window;
- (void)stopObserving:(NSWindow *)window;
- (void)startObservingFontPanel;
- (void)startObservingLookupDismissal;
@end
@implementation WKWindowVisibilityObserver
- (instancetype)initWithView:(NSView *)view impl:(WebKit::WebViewImpl&)impl
{
self = [super init];
if (!self)
return nil;
_view = view;
_impl = &impl;
return self;
}
- (void)dealloc
{
if (canLoadLUNotificationPopoverWillClose())
[[NSNotificationCenter defaultCenter] removeObserver:self name:getLUNotificationPopoverWillClose() object:nil];
[super dealloc];
}
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
static void* keyValueObservingContext = &keyValueObservingContext;
#endif
- (void)startObserving:(NSWindow *)window
{
if (!window)
return;
NSNotificationCenter *defaultNotificationCenter = [NSNotificationCenter defaultCenter];
// An NSView derived object such as WKView cannot observe these notifications, because NSView itself observes them.
[defaultNotificationCenter addObserver:self selector:@selector(_windowDidOrderOffScreen:) name:@"NSWindowDidOrderOffScreenNotification" object:window];
[defaultNotificationCenter addObserver:self selector:@selector(_windowDidOrderOnScreen:) name:@"_NSWindowDidBecomeVisible" object:window];
[defaultNotificationCenter addObserver:self selector:@selector(_windowDidBecomeKey:) name:NSWindowDidBecomeKeyNotification object:nil];
[defaultNotificationCenter addObserver:self selector:@selector(_windowDidResignKey:) name:NSWindowDidResignKeyNotification object:nil];
[defaultNotificationCenter addObserver:self selector:@selector(_windowDidMiniaturize:) name:NSWindowDidMiniaturizeNotification object:window];
[defaultNotificationCenter addObserver:self selector:@selector(_windowDidDeminiaturize:) name:NSWindowDidDeminiaturizeNotification object:window];
[defaultNotificationCenter addObserver:self selector:@selector(_windowDidMove:) name:NSWindowDidMoveNotification object:window];
[defaultNotificationCenter addObserver:self selector:@selector(_windowDidResize:) name:NSWindowDidResizeNotification object:window];
[defaultNotificationCenter addObserver:self selector:@selector(_windowDidChangeBackingProperties:) name:NSWindowDidChangeBackingPropertiesNotification object:window];
[defaultNotificationCenter addObserver:self selector:@selector(_windowDidChangeScreen:) name:NSWindowDidChangeScreenNotification object:window];
[defaultNotificationCenter addObserver:self selector:@selector(_windowDidChangeLayerHosting:) name:@"_NSWindowDidChangeContentsHostedInLayerSurfaceNotification" object:window];
[defaultNotificationCenter addObserver:self selector:@selector(_windowDidChangeOcclusionState:) name:NSWindowDidChangeOcclusionStateNotification object:window];
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
[window addObserver:self forKeyPath:@"contentLayoutRect" options:NSKeyValueObservingOptionInitial context:keyValueObservingContext];
[window addObserver:self forKeyPath:@"titlebarAppearsTransparent" options:NSKeyValueObservingOptionInitial context:keyValueObservingContext];
#endif
}
- (void)stopObserving:(NSWindow *)window
{
if (!window)
return;
NSNotificationCenter *defaultNotificationCenter = [NSNotificationCenter defaultCenter];
[defaultNotificationCenter removeObserver:self name:@"NSWindowDidOrderOffScreenNotification" object:window];
[defaultNotificationCenter removeObserver:self name:@"_NSWindowDidBecomeVisible" object:window];
[defaultNotificationCenter removeObserver:self name:NSWindowDidBecomeKeyNotification object:nil];
[defaultNotificationCenter removeObserver:self name:NSWindowDidResignKeyNotification object:nil];
[defaultNotificationCenter removeObserver:self name:NSWindowDidMiniaturizeNotification object:window];
[defaultNotificationCenter removeObserver:self name:NSWindowDidDeminiaturizeNotification object:window];
[defaultNotificationCenter removeObserver:self name:NSWindowDidMoveNotification object:window];
[defaultNotificationCenter removeObserver:self name:NSWindowDidResizeNotification object:window];
[defaultNotificationCenter removeObserver:self name:NSWindowDidChangeBackingPropertiesNotification object:window];
[defaultNotificationCenter removeObserver:self name:NSWindowDidChangeScreenNotification object:window];
[defaultNotificationCenter removeObserver:self name:@"_NSWindowDidChangeContentsHostedInLayerSurfaceNotification" object:window];
[defaultNotificationCenter removeObserver:self name:NSWindowDidChangeOcclusionStateNotification object:window];
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
if (_impl->isEditable())
[[NSFontPanel sharedFontPanel] removeObserver:self forKeyPath:@"visible" context:keyValueObservingContext];
[window removeObserver:self forKeyPath:@"contentLayoutRect" context:keyValueObservingContext];
[window removeObserver:self forKeyPath:@"titlebarAppearsTransparent" context:keyValueObservingContext];
#endif
}
- (void)startObservingFontPanel
{
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
[[NSFontPanel sharedFontPanel] addObserver:self forKeyPath:@"visible" options:0 context:keyValueObservingContext];
#endif
}
- (void)startObservingLookupDismissal
{
if (canLoadLUNotificationPopoverWillClose())
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_dictionaryLookupPopoverWillClose:) name:getLUNotificationPopoverWillClose() object:nil];
}
- (void)_windowDidOrderOnScreen:(NSNotification *)notification
{
_impl->windowDidOrderOnScreen();
}
- (void)_windowDidOrderOffScreen:(NSNotification *)notification
{
_impl->windowDidOrderOffScreen();
}
- (void)_windowDidBecomeKey:(NSNotification *)notification
{
_impl->windowDidBecomeKey([notification object]);
}
- (void)_windowDidResignKey:(NSNotification *)notification
{
_impl->windowDidResignKey([notification object]);
}
- (void)_windowDidMiniaturize:(NSNotification *)notification
{
_impl->windowDidMiniaturize();
}
- (void)_windowDidDeminiaturize:(NSNotification *)notification
{
_impl->windowDidDeminiaturize();
}
- (void)_windowDidMove:(NSNotification *)notification
{
_impl->windowDidMove();
}
- (void)_windowDidResize:(NSNotification *)notification
{
_impl->windowDidResize();
}
- (void)_windowDidChangeBackingProperties:(NSNotification *)notification
{
CGFloat oldBackingScaleFactor = [[notification.userInfo objectForKey:NSBackingPropertyOldScaleFactorKey] doubleValue];
_impl->windowDidChangeBackingProperties(oldBackingScaleFactor);
}
- (void)_windowDidChangeScreen:(NSNotification *)notification
{
_impl->windowDidChangeScreen();
}
- (void)_windowDidChangeLayerHosting:(NSNotification *)notification
{
_impl->windowDidChangeLayerHosting();
}
- (void)_windowDidChangeOcclusionState:(NSNotification *)notification
{
_impl->windowDidChangeOcclusionState();
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
if (context != keyValueObservingContext) {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
return;
}
if ([keyPath isEqualToString:@"visible"] && [NSFontPanel sharedFontPanelExists] && object == [NSFontPanel sharedFontPanel]) {
_impl->updateFontPanelIfNeeded();
return;
}
if ([keyPath isEqualToString:@"contentLayoutRect"] || [keyPath isEqualToString:@"titlebarAppearsTransparent"])
_impl->updateContentInsetsIfAutomatic();
#endif
}
- (void)_dictionaryLookupPopoverWillClose:(NSNotification *)notification
{
_impl->clearTextIndicatorWithAnimation(WebCore::TextIndicatorWindowDismissalAnimation::None);
}
@end
@interface WKEditCommandObjC : NSObject {
RefPtr<WebKit::WebEditCommandProxy> m_command;
}
- (id)initWithWebEditCommandProxy:(RefPtr<WebKit::WebEditCommandProxy>)command;
- (WebKit::WebEditCommandProxy*)command;
@end
@interface WKEditorUndoTargetObjC : NSObject
- (void)undoEditing:(id)sender;
- (void)redoEditing:(id)sender;
@end
@implementation WKEditCommandObjC
- (id)initWithWebEditCommandProxy:(RefPtr<WebKit::WebEditCommandProxy>)command
{
self = [super init];
if (!self)
return nil;
m_command = command;
return self;
}
- (WebKit::WebEditCommandProxy*)command
{
return m_command.get();
}
@end
@implementation WKEditorUndoTargetObjC
- (void)undoEditing:(id)sender
{
ASSERT([sender isKindOfClass:[WKEditCommandObjC class]]);
[sender command]->unapply();
}
- (void)redoEditing:(id)sender
{
ASSERT([sender isKindOfClass:[WKEditCommandObjC class]]);
[sender command]->reapply();
}
@end
@interface WKFlippedView : NSView
@end
@implementation WKFlippedView
- (BOOL)isFlipped
{
return YES;
}
@end
namespace WebKit {
static NSTrackingAreaOptions trackingAreaOptions()
{
// Legacy style scrollbars have design details that rely on tracking the mouse all the time.
NSTrackingAreaOptions options = NSTrackingMouseMoved | NSTrackingMouseEnteredAndExited | NSTrackingInVisibleRect | NSTrackingCursorUpdate;
if (WKRecommendedScrollerStyle() == NSScrollerStyleLegacy)
options |= NSTrackingActiveAlways;
else
options |= NSTrackingActiveInKeyWindow;
return options;
}
WebViewImpl::WebViewImpl(NSView <WebViewImplDelegate> *view, WebPageProxy& page, PageClient& pageClient)
: m_view(view)
, m_page(page)
, m_pageClient(pageClient)
, m_weakPtrFactory(this)
, m_needsViewFrameInWindowCoordinates(m_page.preferences().pluginsEnabled())
, m_intrinsicContentSize(CGSizeMake(NSViewNoInstrinsicMetric, NSViewNoInstrinsicMetric))
, m_layoutStrategy([WKViewLayoutStrategy layoutStrategyWithPage:m_page view:m_view viewImpl:*this mode:kWKLayoutModeViewSize])
, m_undoTarget(adoptNS([[WKEditorUndoTargetObjC alloc] init]))
, m_windowVisibilityObserver(adoptNS([[WKWindowVisibilityObserver alloc] initWithView:view impl:*this]))
, m_primaryTrackingArea(adoptNS([[NSTrackingArea alloc] initWithRect:m_view.frame options:trackingAreaOptions() owner:m_view userInfo:nil]))
{
[m_view addTrackingArea:m_primaryTrackingArea.get()];
m_page.setIntrinsicDeviceScaleFactor(intrinsicDeviceScaleFactor());
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
if (Class gestureClass = NSClassFromString(@"NSImmediateActionGestureRecognizer")) {
m_immediateActionGestureRecognizer = adoptNS([(NSImmediateActionGestureRecognizer *)[gestureClass alloc] init]);
m_immediateActionController = adoptNS([[WKImmediateActionController alloc] initWithPage:m_page view:m_view viewImpl:*this recognizer:m_immediateActionGestureRecognizer.get()]);
[m_immediateActionGestureRecognizer setDelegate:m_immediateActionController.get()];
[m_immediateActionGestureRecognizer setDelaysPrimaryMouseButtonEvents:NO];
}
#endif
}
WebViewImpl::~WebViewImpl()
{
ASSERT(!m_inSecureInputState);
#if WK_API_ENABLED
ASSERT(!m_thumbnailView);
#endif
[m_layoutStrategy invalidate];
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
[m_immediateActionController willDestroyView:m_view];
#endif
}
void WebViewImpl::setDrawsBackground(bool drawsBackground)
{
m_page.setDrawsBackground(drawsBackground);
}
bool WebViewImpl::drawsBackground() const
{
return m_page.drawsBackground();
}
void WebViewImpl::setDrawsTransparentBackground(bool drawsTransparentBackground)
{
m_page.setDrawsTransparentBackground(drawsTransparentBackground);
}
bool WebViewImpl::drawsTransparentBackground() const
{
return m_page.drawsTransparentBackground();
}
bool WebViewImpl::acceptsFirstResponder()
{
return true;
}
bool WebViewImpl::becomeFirstResponder()
{
// If we just became first responder again, there is no need to do anything,
// since resignFirstResponder has correctly detected this situation.
if (m_willBecomeFirstResponderAgain) {
m_willBecomeFirstResponderAgain = false;
return true;
}
NSSelectionDirection direction = [[m_view window] keyViewSelectionDirection];
m_inBecomeFirstResponder = true;
updateSecureInputState();
m_page.viewStateDidChange(WebCore::ViewState::IsFocused);
// Restore the selection in the editable region if resigning first responder cleared selection.
m_page.restoreSelectionInFocusedEditableElement();
m_inBecomeFirstResponder = false;
if (direction != NSDirectSelection) {
NSEvent *event = [NSApp currentEvent];
NSEvent *keyboardEvent = nil;
if ([event type] == NSKeyDown || [event type] == NSKeyUp)
keyboardEvent = event;
m_page.setInitialFocus(direction == NSSelectingNext, keyboardEvent != nil, NativeWebKeyboardEvent(keyboardEvent, false, Vector<WebCore::KeypressCommand>()), [](WebKit::CallbackBase::Error) { });
}
return true;
}
bool WebViewImpl::resignFirstResponder()
{
#if WK_API_ENABLED
// Predict the case where we are losing first responder status only to
// gain it back again. We want resignFirstResponder to do nothing in that case.
id nextResponder = [[m_view window] _newFirstResponderAfterResigning];
// FIXME: This will probably need to change once WKWebView doesn't contain a WKView.
if ([nextResponder isKindOfClass:[WKWebView class]] && m_view.superview == nextResponder) {
m_willBecomeFirstResponderAgain = true;
return true;
}
#endif
m_willBecomeFirstResponderAgain = false;
m_inResignFirstResponder = true;
#if USE(ASYNC_NSTEXTINPUTCLIENT)
m_page.confirmCompositionAsync();
#else
if (m_page.editorState().hasComposition && !m_page.editorState().shouldIgnoreCompositionSelectionChange)
m_page.cancelComposition();
#endif
notifyInputContextAboutDiscardedComposition();
resetSecureInputState();
if (!m_page.maintainsInactiveSelection())
m_page.clearSelection();
m_page.viewStateDidChange(WebCore::ViewState::IsFocused);
m_inResignFirstResponder = false;
return true;
}
bool WebViewImpl::isFocused() const
{
if (m_inBecomeFirstResponder)
return true;
if (m_inResignFirstResponder)
return false;
return m_view.window.firstResponder == m_view;
}
void WebViewImpl::viewWillStartLiveResize()
{
m_page.viewWillStartLiveResize();
[m_layoutStrategy willStartLiveResize];
}
void WebViewImpl::viewDidEndLiveResize()
{
m_page.viewWillEndLiveResize();
[m_layoutStrategy didEndLiveResize];
}
void WebViewImpl::renewGState()
{
if (m_textIndicatorWindow)
dismissContentRelativeChildWindowsWithAnimation(false);
// Update the view frame.
if (m_view.window)
updateWindowAndViewFrames();
updateContentInsetsIfAutomatic();
}
void WebViewImpl::setFrameSize(CGSize)
{
[m_layoutStrategy didChangeFrameSize];
}
void WebViewImpl::disableFrameSizeUpdates()
{
[m_layoutStrategy disableFrameSizeUpdates];
}
void WebViewImpl::enableFrameSizeUpdates()
{
[m_layoutStrategy enableFrameSizeUpdates];
}
bool WebViewImpl::frameSizeUpdatesDisabled() const
{
return [m_layoutStrategy frameSizeUpdatesDisabled];
}
void WebViewImpl::setFrameAndScrollBy(CGRect frame, CGSize offset)
{
ASSERT(CGSizeEqualToSize(m_resizeScrollOffset, CGSizeZero));
m_resizeScrollOffset = offset;
m_view.frame = NSRectFromCGRect(frame);
}
void WebViewImpl::updateWindowAndViewFrames()
{
if (clipsToVisibleRect())
updateViewExposedRect();
if (m_didScheduleWindowAndViewFrameUpdate)
return;
m_didScheduleWindowAndViewFrameUpdate = true;
auto weakThis = createWeakPtr();
dispatch_async(dispatch_get_main_queue(), [weakThis] {
if (!weakThis)
return;
weakThis->m_didScheduleWindowAndViewFrameUpdate = false;
NSRect viewFrameInWindowCoordinates = NSZeroRect;
NSPoint accessibilityPosition = NSZeroPoint;
if (weakThis->m_needsViewFrameInWindowCoordinates)
viewFrameInWindowCoordinates = [weakThis->m_view convertRect:weakThis->m_view.frame toView:nil];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if (WebCore::AXObjectCache::accessibilityEnabled())
accessibilityPosition = [[weakThis->m_view accessibilityAttributeValue:NSAccessibilityPositionAttribute] pointValue];
#pragma clang diagnostic pop
weakThis->m_page.windowAndViewFramesChanged(viewFrameInWindowCoordinates, accessibilityPosition);
});
}
void WebViewImpl::setFixedLayoutSize(CGSize fixedLayoutSize)
{
m_page.setFixedLayoutSize(WebCore::expandedIntSize(WebCore::FloatSize(fixedLayoutSize)));
}
CGSize WebViewImpl::fixedLayoutSize() const
{
return m_page.fixedLayoutSize();
}
void WebViewImpl::setDrawingAreaSize(CGSize size)
{
if (!m_page.drawingArea())
return;
m_page.drawingArea()->setSize(WebCore::IntSize(size), WebCore::IntSize(), WebCore::IntSize(m_resizeScrollOffset));
m_resizeScrollOffset = CGSizeZero;
}
void WebViewImpl::setAutomaticallyAdjustsContentInsets(bool automaticallyAdjustsContentInsets)
{
m_automaticallyAdjustsContentInsets = automaticallyAdjustsContentInsets;
updateContentInsetsIfAutomatic();
}
void WebViewImpl::updateContentInsetsIfAutomatic()
{
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
if (!m_automaticallyAdjustsContentInsets)
return;
NSWindow *window = m_view.window;
if ((window.styleMask & NSFullSizeContentViewWindowMask) && !window.titlebarAppearsTransparent && ![m_view enclosingScrollView]) {
NSRect contentLayoutRect = [m_view convertRect:window.contentLayoutRect fromView:nil];
CGFloat newTopContentInset = NSMaxY(contentLayoutRect) - NSHeight(contentLayoutRect);
if (m_topContentInset != newTopContentInset)
setTopContentInset(newTopContentInset);
} else
setTopContentInset(0);
#endif
}
void WebViewImpl::setTopContentInset(CGFloat contentInset)
{
m_topContentInset = contentInset;
if (m_didScheduleSetTopContentInset)
return;
m_didScheduleSetTopContentInset = true;
auto weakThis = createWeakPtr();
dispatch_async(dispatch_get_main_queue(), [weakThis] {
if (!weakThis)
return;
weakThis->dispatchSetTopContentInset();
});
}
void WebViewImpl::dispatchSetTopContentInset()
{
if (!m_didScheduleSetTopContentInset)
return;
m_didScheduleSetTopContentInset = false;
m_page.setTopContentInset(m_topContentInset);
}
void WebViewImpl::setContentPreparationRect(CGRect rect)
{
m_contentPreparationRect = rect;
m_useContentPreparationRectForVisibleRect = true;
}
void WebViewImpl::updateViewExposedRect()
{
CGRect exposedRect = NSRectToCGRect([m_view visibleRect]);
if (m_useContentPreparationRectForVisibleRect)
exposedRect = CGRectUnion(m_contentPreparationRect, exposedRect);
if (auto drawingArea = m_page.drawingArea())
drawingArea->setExposedRect(m_clipsToVisibleRect ? WebCore::FloatRect(exposedRect) : WebCore::FloatRect::infiniteRect());
}
void WebViewImpl::setClipsToVisibleRect(bool clipsToVisibleRect)
{
m_clipsToVisibleRect = clipsToVisibleRect;
updateViewExposedRect();
}
void WebViewImpl::setIntrinsicContentSize(CGSize intrinsicContentSize)
{
// If the intrinsic content size is less than the minimum layout width, the content flowed to fit,
// so we can report that that dimension is flexible. If not, we need to report our intrinsic width
// so that autolayout will know to provide space for us.
CGSize intrinsicContentSizeAcknowledgingFlexibleWidth = intrinsicContentSize;
if (intrinsicContentSize.width < m_page.minimumLayoutSize().width())
intrinsicContentSizeAcknowledgingFlexibleWidth.width = NSViewNoInstrinsicMetric;
m_intrinsicContentSize = intrinsicContentSizeAcknowledgingFlexibleWidth;
[m_view invalidateIntrinsicContentSize];
}
CGSize WebViewImpl::intrinsicContentSize() const
{
return m_intrinsicContentSize;
}
void WebViewImpl::setViewScale(CGFloat viewScale)
{
m_lastRequestedViewScale = viewScale;
if (!supportsArbitraryLayoutModes() && viewScale != 1)
return;
if (viewScale <= 0 || isnan(viewScale) || isinf(viewScale))
[NSException raise:NSInvalidArgumentException format:@"View scale should be a positive number"];
m_page.scaleView(viewScale);
[m_layoutStrategy didChangeViewScale];
}
CGFloat WebViewImpl::viewScale() const
{
return m_page.viewScaleFactor();
}
WKLayoutMode WebViewImpl::layoutMode() const
{
return [m_layoutStrategy layoutMode];
}
void WebViewImpl::setLayoutMode(WKLayoutMode layoutMode)
{
m_lastRequestedLayoutMode = layoutMode;
if (!supportsArbitraryLayoutModes() && layoutMode != kWKLayoutModeViewSize)
return;
if (layoutMode == [m_layoutStrategy layoutMode])
return;
[m_layoutStrategy willChangeLayoutStrategy];
m_layoutStrategy = [WKViewLayoutStrategy layoutStrategyWithPage:m_page view:m_view viewImpl:*this mode:layoutMode];
}
bool WebViewImpl::supportsArbitraryLayoutModes() const
{
if ([m_fullScreenWindowController isFullScreen])
return false;
WebFrameProxy* frame = m_page.mainFrame();
if (!frame)
return true;
// If we have a plugin document in the main frame, avoid using custom WKLayoutModes
// and fall back to the defaults, because there's a good chance that it won't work (e.g. with PDFPlugin).
if (frame->containsPluginDocument())
return false;
return true;
}
void WebViewImpl::updateSupportsArbitraryLayoutModes()
{
if (!supportsArbitraryLayoutModes()) {
WKLayoutMode oldRequestedLayoutMode = m_lastRequestedLayoutMode;
CGFloat oldRequestedViewScale = m_lastRequestedViewScale;
setViewScale(1);
setLayoutMode(kWKLayoutModeViewSize);
// The 'last requested' parameters will have been overwritten by setting them above, but we don't
// want this to count as a request (only changes from the client count), so reset them.
m_lastRequestedLayoutMode = oldRequestedLayoutMode;
m_lastRequestedViewScale = oldRequestedViewScale;
} else if (m_lastRequestedLayoutMode != [m_layoutStrategy layoutMode]) {
setViewScale(m_lastRequestedViewScale);
setLayoutMode(m_lastRequestedLayoutMode);
}
}
void WebViewImpl::setOverrideDeviceScaleFactor(CGFloat deviceScaleFactor)
{
m_overrideDeviceScaleFactor = deviceScaleFactor;
m_page.setIntrinsicDeviceScaleFactor(intrinsicDeviceScaleFactor());
}
float WebViewImpl::intrinsicDeviceScaleFactor() const
{
if (m_overrideDeviceScaleFactor)
return m_overrideDeviceScaleFactor;
if (m_targetWindowForMovePreparation)
return m_targetWindowForMovePreparation.backingScaleFactor;
if (NSWindow *window = m_view.window)
return window.backingScaleFactor;
return [NSScreen mainScreen].backingScaleFactor;
}
void WebViewImpl::windowDidOrderOffScreen()
{
m_page.viewStateDidChange(WebCore::ViewState::IsVisible | WebCore::ViewState::WindowIsActive);
}
void WebViewImpl::windowDidOrderOnScreen()
{
m_page.viewStateDidChange(WebCore::ViewState::IsVisible | WebCore::ViewState::WindowIsActive);
}
void WebViewImpl::windowDidBecomeKey(NSWindow *keyWindow)
{
if (keyWindow == m_view.window || keyWindow == m_view.window.attachedSheet) {
updateSecureInputState();
m_page.viewStateDidChange(WebCore::ViewState::WindowIsActive);
}
}
void WebViewImpl::windowDidResignKey(NSWindow *formerKeyWindow)
{
if (formerKeyWindow == m_view.window || formerKeyWindow == m_view.window.attachedSheet) {
updateSecureInputState();
m_page.viewStateDidChange(WebCore::ViewState::WindowIsActive);
}
}
void WebViewImpl::windowDidMiniaturize()
{
m_page.viewStateDidChange(WebCore::ViewState::IsVisible);
}
void WebViewImpl::windowDidDeminiaturize()
{
m_page.viewStateDidChange(WebCore::ViewState::IsVisible);
}
void WebViewImpl::windowDidMove()
{
updateWindowAndViewFrames();
}
void WebViewImpl::windowDidResize()
{
updateWindowAndViewFrames();
}
void WebViewImpl::windowDidChangeBackingProperties(CGFloat oldBackingScaleFactor)
{
CGFloat newBackingScaleFactor = intrinsicDeviceScaleFactor();
if (oldBackingScaleFactor == newBackingScaleFactor)
return;
m_page.setIntrinsicDeviceScaleFactor(newBackingScaleFactor);
}
void WebViewImpl::windowDidChangeScreen()
{
NSWindow *window = m_targetWindowForMovePreparation ? m_targetWindowForMovePreparation : m_view.window;
m_page.windowScreenDidChange((PlatformDisplayID)[[[[window screen] deviceDescription] objectForKey:@"NSScreenNumber"] intValue]);
}
void WebViewImpl::windowDidChangeLayerHosting()
{
m_page.layerHostingModeDidChange();
}
void WebViewImpl::windowDidChangeOcclusionState()
{
m_page.viewStateDidChange(WebCore::ViewState::IsVisible);
}
void WebViewImpl::viewWillMoveToWindow(NSWindow *window)
{
// If we're in the middle of preparing to move to a window, we should only be moved to that window.
ASSERT(!m_targetWindowForMovePreparation || (m_targetWindowForMovePreparation == window));
NSWindow *currentWindow = m_view.window;
if (window == currentWindow)
return;
clearAllEditCommands();
NSWindow *stopObservingWindow = m_targetWindowForMovePreparation ? m_targetWindowForMovePreparation : m_view.window;
[m_windowVisibilityObserver stopObserving:stopObservingWindow];
[m_windowVisibilityObserver startObserving:window];
}
void WebViewImpl::viewDidMoveToWindow()
{
NSWindow *window = m_targetWindowForMovePreparation ? m_targetWindowForMovePreparation : m_view.window;
if (window) {
windowDidChangeScreen();
WebCore::ViewState::Flags viewStateChanges = WebCore::ViewState::WindowIsActive | WebCore::ViewState::IsVisible;
if (m_isDeferringViewInWindowChanges)
m_viewInWindowChangeWasDeferred = true;
else
viewStateChanges |= WebCore::ViewState::IsInWindow;
m_page.viewStateDidChange(viewStateChanges);
updateWindowAndViewFrames();
// FIXME(135509) This call becomes unnecessary once 135509 is fixed; remove.
m_page.layerHostingModeDidChange();
if (!m_flagsChangedEventMonitor) {
auto weakThis = createWeakPtr();
m_flagsChangedEventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSFlagsChangedMask handler:[weakThis] (NSEvent *flagsChangedEvent) {
if (weakThis)
weakThis->postFakeMouseMovedEventForFlagsChangedEvent(flagsChangedEvent);
return flagsChangedEvent;
}];
}
accessibilityRegisterUIProcessTokens();
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
if (m_immediateActionGestureRecognizer && ![[m_view gestureRecognizers] containsObject:m_immediateActionGestureRecognizer.get()] && !m_ignoresNonWheelEvents && m_allowsLinkPreview)
[m_view addGestureRecognizer:m_immediateActionGestureRecognizer.get()];
#endif
} else {
WebCore::ViewState::Flags viewStateChanges = WebCore::ViewState::WindowIsActive | WebCore::ViewState::IsVisible;
if (m_isDeferringViewInWindowChanges)
m_viewInWindowChangeWasDeferred = true;
else
viewStateChanges |= WebCore::ViewState::IsInWindow;
m_page.viewStateDidChange(viewStateChanges);
[NSEvent removeMonitor:m_flagsChangedEventMonitor];
m_flagsChangedEventMonitor = nil;
dismissContentRelativeChildWindowsWithAnimation(false);
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
if (m_immediateActionGestureRecognizer)
[m_view removeGestureRecognizer:m_immediateActionGestureRecognizer.get()];
#endif
}
m_page.setIntrinsicDeviceScaleFactor(intrinsicDeviceScaleFactor());
}
void WebViewImpl::viewDidChangeBackingProperties()
{
NSColorSpace *colorSpace = m_view.window.colorSpace;
if ([colorSpace isEqualTo:m_colorSpace.get()])
return;
m_colorSpace = nullptr;
if (DrawingAreaProxy *drawingArea = m_page.drawingArea())
drawingArea->colorSpaceDidChange();
}
void WebViewImpl::postFakeMouseMovedEventForFlagsChangedEvent(NSEvent *flagsChangedEvent)
{
NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved location:flagsChangedEvent.window.mouseLocationOutsideOfEventStream
modifierFlags:flagsChangedEvent.modifierFlags timestamp:flagsChangedEvent.timestamp windowNumber:flagsChangedEvent.windowNumber
context:flagsChangedEvent.context eventNumber:0 clickCount:0 pressure:0];
NativeWebMouseEvent webEvent(fakeEvent, m_lastPressureEvent.get(), m_view);
m_page.handleMouseEvent(webEvent);
}
ColorSpaceData WebViewImpl::colorSpace()
{
if (!m_colorSpace) {
if (m_targetWindowForMovePreparation)
m_colorSpace = m_targetWindowForMovePreparation.colorSpace;
else if (NSWindow *window = m_view.window)
m_colorSpace = window.colorSpace;
else
m_colorSpace = [NSScreen mainScreen].colorSpace;
}
ColorSpaceData colorSpaceData;
colorSpaceData.cgColorSpace = [m_colorSpace CGColorSpace];
return colorSpaceData;
}
void WebViewImpl::beginDeferringViewInWindowChanges()
{
if (m_shouldDeferViewInWindowChanges) {
NSLog(@"beginDeferringViewInWindowChanges was called while already deferring view-in-window changes!");
return;
}
m_shouldDeferViewInWindowChanges = true;
}
void WebViewImpl::endDeferringViewInWindowChanges()
{
if (!m_shouldDeferViewInWindowChanges) {
NSLog(@"endDeferringViewInWindowChanges was called without beginDeferringViewInWindowChanges!");
return;
}
m_shouldDeferViewInWindowChanges = false;
if (m_viewInWindowChangeWasDeferred) {
dispatchSetTopContentInset();
m_page.viewStateDidChange(WebCore::ViewState::IsInWindow);
m_viewInWindowChangeWasDeferred = false;
}
}
void WebViewImpl::endDeferringViewInWindowChangesSync()
{
if (!m_shouldDeferViewInWindowChanges) {
NSLog(@"endDeferringViewInWindowChangesSync was called without beginDeferringViewInWindowChanges!");
return;
}
m_shouldDeferViewInWindowChanges = false;
if (m_viewInWindowChangeWasDeferred) {
dispatchSetTopContentInset();
m_page.viewStateDidChange(WebCore::ViewState::IsInWindow);
m_viewInWindowChangeWasDeferred = false;
}
}
void WebViewImpl::prepareForMoveToWindow(NSWindow *targetWindow, std::function<void()> completionHandler)
{
m_shouldDeferViewInWindowChanges = true;
viewWillMoveToWindow(targetWindow);
m_targetWindowForMovePreparation = targetWindow;
viewDidMoveToWindow();
m_shouldDeferViewInWindowChanges = false;
auto weakThis = createWeakPtr();
m_page.installViewStateChangeCompletionHandler([weakThis, completionHandler]() {
completionHandler();
if (!weakThis)
return;
ASSERT(weakThis->m_view.window == weakThis->m_targetWindowForMovePreparation);
weakThis->m_targetWindowForMovePreparation = nil;
});
dispatchSetTopContentInset();
m_page.viewStateDidChange(WebCore::ViewState::IsInWindow, false, WebPageProxy::ViewStateChangeDispatchMode::Immediate);
m_viewInWindowChangeWasDeferred = false;
}
void WebViewImpl::updateSecureInputState()
{
if (![[m_view window] isKeyWindow] || !isFocused()) {
if (m_inSecureInputState) {
DisableSecureEventInput();
m_inSecureInputState = false;
}
return;
}
// WKView has a single input context for all editable areas (except for plug-ins).
NSTextInputContext *context = [m_view _superInputContext];
bool isInPasswordField = m_page.editorState().isInPasswordField;
if (isInPasswordField) {
if (!m_inSecureInputState)
EnableSecureEventInput();
static NSArray *romanInputSources = [[NSArray alloc] initWithObjects:&NSAllRomanInputSourcesLocaleIdentifier count:1];
LOG(TextInput, "-> setAllowedInputSourceLocales:romanInputSources");
[context setAllowedInputSourceLocales:romanInputSources];
} else {
if (m_inSecureInputState)
DisableSecureEventInput();
LOG(TextInput, "-> setAllowedInputSourceLocales:nil");
[context setAllowedInputSourceLocales:nil];
}
m_inSecureInputState = isInPasswordField;
}
void WebViewImpl::resetSecureInputState()
{
if (m_inSecureInputState) {
DisableSecureEventInput();
m_inSecureInputState = false;
}
}
void WebViewImpl::notifyInputContextAboutDiscardedComposition()
{
// <rdar://problem/9359055>: -discardMarkedText can only be called for active contexts.
// FIXME: We fail to ever notify the input context if something (e.g. a navigation) happens while the window is not key.
// This is not a problem when the window is key, because we discard marked text on resigning first responder.
if (![[m_view window] isKeyWindow] || m_view != [[m_view window] firstResponder])
return;
LOG(TextInput, "-> discardMarkedText");
[[m_view _superInputContext] discardMarkedText]; // Inform the input method that we won't have an inline input area despite having been asked to.
}
void WebViewImpl::setPluginComplexTextInputState(PluginComplexTextInputState pluginComplexTextInputState)
{
m_pluginComplexTextInputState = pluginComplexTextInputState;
if (m_pluginComplexTextInputState != PluginComplexTextInputDisabled)
return;
// Send back an empty string to the plug-in. This will disable text input.
m_page.sendComplexTextInputToPlugin(m_pluginComplexTextInputIdentifier, String());
}
void WebViewImpl::setPluginComplexTextInputStateAndIdentifier(PluginComplexTextInputState pluginComplexTextInputState, uint64_t pluginComplexTextInputIdentifier)
{
if (pluginComplexTextInputIdentifier != m_pluginComplexTextInputIdentifier) {
// We're asked to update the state for a plug-in that doesn't have focus.
return;
}
setPluginComplexTextInputState(pluginComplexTextInputState);
}
void WebViewImpl::disableComplexTextInputIfNecessary()
{
if (!m_pluginComplexTextInputIdentifier)
return;
if (m_pluginComplexTextInputState != PluginComplexTextInputEnabled)
return;
// Check if the text input window has been dismissed.
if (![[WKTextInputWindowController sharedTextInputWindowController] hasMarkedText])
setPluginComplexTextInputState(PluginComplexTextInputDisabled);
}
bool WebViewImpl::handlePluginComplexTextInputKeyDown(NSEvent *event)
{
ASSERT(m_pluginComplexTextInputIdentifier);
ASSERT(m_pluginComplexTextInputState != PluginComplexTextInputDisabled);
BOOL usingLegacyCocoaTextInput = m_pluginComplexTextInputState == PluginComplexTextInputEnabledLegacy;
NSString *string = nil;
BOOL didHandleEvent = [[WKTextInputWindowController sharedTextInputWindowController] interpretKeyEvent:event usingLegacyCocoaTextInput:usingLegacyCocoaTextInput string:&string];
if (string) {
m_page.sendComplexTextInputToPlugin(m_pluginComplexTextInputIdentifier, string);
if (!usingLegacyCocoaTextInput)
m_pluginComplexTextInputState = PluginComplexTextInputDisabled;
}
return didHandleEvent;
}
bool WebViewImpl::tryHandlePluginComplexTextInputKeyDown(NSEvent *event)
{
if (!m_pluginComplexTextInputIdentifier || m_pluginComplexTextInputState == PluginComplexTextInputDisabled)
return NO;
// Check if the text input window has been dismissed and let the plug-in process know.
// This is only valid with the updated Cocoa text input spec.
disableComplexTextInputIfNecessary();
// Try feeding the keyboard event directly to the plug-in.
if (m_pluginComplexTextInputState == PluginComplexTextInputEnabledLegacy)
return handlePluginComplexTextInputKeyDown(event);
return NO;
}
void WebViewImpl::pluginFocusOrWindowFocusChanged(bool pluginHasFocusAndWindowHasFocus, uint64_t pluginComplexTextInputIdentifier)
{
BOOL inputSourceChanged = m_pluginComplexTextInputIdentifier;
if (pluginHasFocusAndWindowHasFocus) {
// Check if we're already allowing text input for this plug-in.
if (pluginComplexTextInputIdentifier == m_pluginComplexTextInputIdentifier)
return;
m_pluginComplexTextInputIdentifier = pluginComplexTextInputIdentifier;
} else {
// Check if we got a request to unfocus a plug-in that isn't focused.
if (pluginComplexTextInputIdentifier != m_pluginComplexTextInputIdentifier)
return;
m_pluginComplexTextInputIdentifier = 0;
}
if (inputSourceChanged) {
// The input source changed; discard any entered text.
[[WKTextInputWindowController sharedTextInputWindowController] unmarkText];
}
// This will force the current input context to be updated to its correct value.
[NSApp updateWindows];
}
bool WebViewImpl::tryPostProcessPluginComplexTextInputKeyDown(NSEvent *event)
{
if (!m_pluginComplexTextInputIdentifier || m_pluginComplexTextInputState == PluginComplexTextInputDisabled)
return NO;
// In the legacy text input model, the event has already been sent to the input method.
if (m_pluginComplexTextInputState == PluginComplexTextInputEnabledLegacy)
return NO;
return handlePluginComplexTextInputKeyDown(event);
}
void WebViewImpl::pressureChangeWithEvent(NSEvent *event)
{
#if defined(__LP64__) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 101003
if (event == m_lastPressureEvent)
return;
if (m_ignoresNonWheelEvents)
return;
if (event.phase != NSEventPhaseChanged && event.phase != NSEventPhaseBegan && event.phase != NSEventPhaseEnded)
return;
NativeWebMouseEvent webEvent(event, m_lastPressureEvent.get(), m_view);
m_page.handleMouseEvent(webEvent);
m_lastPressureEvent = event;
#endif
}
#if ENABLE(FULLSCREEN_API)
bool WebViewImpl::hasFullScreenWindowController() const
{
return !!m_fullScreenWindowController;
}
WKFullScreenWindowController *WebViewImpl::fullScreenWindowController()
{
if (!m_fullScreenWindowController)
m_fullScreenWindowController = adoptNS([[WKFullScreenWindowController alloc] initWithWindow:createFullScreenWindow() webView:m_view page:m_page]);
return m_fullScreenWindowController.get();
}
void WebViewImpl::closeFullScreenWindowController()
{
if (!m_fullScreenWindowController)
return;
[m_fullScreenWindowController close];
m_fullScreenWindowController = nullptr;
}
#endif
NSView *WebViewImpl::fullScreenPlaceholderView()
{
#if ENABLE(FULLSCREEN_API)
if (m_fullScreenWindowController && [m_fullScreenWindowController isFullScreen])
return [m_fullScreenWindowController webViewPlaceholder];
#endif
return nil;
}
NSWindow *WebViewImpl::createFullScreenWindow()
{
#if ENABLE(FULLSCREEN_API)
return [[[WebCoreFullScreenWindow alloc] initWithContentRect:[[NSScreen mainScreen] frame] styleMask:(NSBorderlessWindowMask | NSResizableWindowMask) backing:NSBackingStoreBuffered defer:NO] autorelease];
#else
return nil;
#endif
}
bool WebViewImpl::isEditable() const
{
return m_page.isEditable();
}
void WebViewImpl::executeEditCommand(const String& commandName, const String& argument)
{
m_page.executeEditCommand(commandName, argument);
}
void WebViewImpl::registerEditCommand(RefPtr<WebEditCommandProxy> prpCommand, WebPageProxy::UndoOrRedo undoOrRedo)
{
RefPtr<WebEditCommandProxy> command = prpCommand;
RetainPtr<WKEditCommandObjC> commandObjC = adoptNS([[WKEditCommandObjC alloc] initWithWebEditCommandProxy:command]);
String actionName = WebEditCommandProxy::nameForEditAction(command->editAction());
NSUndoManager *undoManager = [m_view undoManager];
[undoManager registerUndoWithTarget:m_undoTarget.get() selector:((undoOrRedo == WebPageProxy::Undo) ? @selector(undoEditing:) : @selector(redoEditing:)) object:commandObjC.get()];
if (!actionName.isEmpty())
[undoManager setActionName:(NSString *)actionName];
}
void WebViewImpl::clearAllEditCommands()
{
[[m_view undoManager] removeAllActionsWithTarget:m_undoTarget.get()];
}
bool WebViewImpl::writeSelectionToPasteboard(NSPasteboard *pasteboard, NSArray *types)
{
size_t numTypes = types.count;
[pasteboard declareTypes:types owner:nil];
for (size_t i = 0; i < numTypes; ++i) {
if ([[types objectAtIndex:i] isEqualTo:NSStringPboardType])
[pasteboard setString:m_page.stringSelectionForPasteboard() forType:NSStringPboardType];
else {
RefPtr<WebCore::SharedBuffer> buffer = m_page.dataSelectionForPasteboard([types objectAtIndex:i]);
[pasteboard setData:buffer ? buffer->createNSData().get() : nil forType:[types objectAtIndex:i]];
}
}
return true;
}
void WebViewImpl::centerSelectionInVisibleArea()
{
m_page.centerSelectionInVisibleArea();
}
void WebViewImpl::selectionDidChange()
{
updateFontPanelIfNeeded();
}
void WebViewImpl::startObservingFontPanel()
{
[m_windowVisibilityObserver startObservingFontPanel];
}
void WebViewImpl::updateFontPanelIfNeeded()
{
const EditorState& editorState = m_page.editorState();
if (editorState.selectionIsNone || !editorState.isContentEditable)
return;
if ([NSFontPanel sharedFontPanelExists] && [[NSFontPanel sharedFontPanel] isVisible]) {
m_page.fontAtSelection([](const String& fontName, double fontSize, bool selectionHasMultipleFonts, WebKit::CallbackBase::Error error) {
NSFont *font = [NSFont fontWithName:fontName size:fontSize];
if (font)
[[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:selectionHasMultipleFonts];
});
}
}
void WebViewImpl::preferencesDidChange()
{
BOOL needsViewFrameInWindowCoordinates = m_page.preferences().pluginsEnabled();
if (!!needsViewFrameInWindowCoordinates == !!m_needsViewFrameInWindowCoordinates)
return;
m_needsViewFrameInWindowCoordinates = needsViewFrameInWindowCoordinates;
if (m_view.window)
updateWindowAndViewFrames();
}
void WebViewImpl::setTextIndicator(WebCore::TextIndicator& textIndicator, WebCore::TextIndicatorWindowLifetime lifetime)
{
if (!m_textIndicatorWindow)
m_textIndicatorWindow = std::make_unique<WebCore::TextIndicatorWindow>(m_view);
NSRect textBoundingRectInScreenCoordinates = [m_view.window convertRectToScreen:[m_view convertRect:textIndicator.textBoundingRectInRootViewCoordinates() toView:nil]];
m_textIndicatorWindow->setTextIndicator(textIndicator, NSRectToCGRect(textBoundingRectInScreenCoordinates), lifetime);
}
void WebViewImpl::clearTextIndicatorWithAnimation(WebCore::TextIndicatorWindowDismissalAnimation animation)
{
if (m_textIndicatorWindow)
m_textIndicatorWindow->clearTextIndicator(animation);
m_textIndicatorWindow = nullptr;
}
void WebViewImpl::setTextIndicatorAnimationProgress(float progress)
{
if (m_textIndicatorWindow)
m_textIndicatorWindow->setAnimationProgress(progress);
}
void WebViewImpl::dismissContentRelativeChildWindowsWithAnimation(bool animate)
{
[m_view _dismissContentRelativeChildWindowsWithAnimation:animate];
}
void WebViewImpl::dismissContentRelativeChildWindowsWithAnimationFromViewOnly(bool animate)
{
// Calling _clearTextIndicatorWithAnimation here will win out over the animated clear in dismissContentRelativeChildWindowsFromViewOnly.
// We can't invert these because clients can override (and have overridden) _dismissContentRelativeChildWindows, so it needs to be called.
// For this same reason, this can't be moved to WebViewImpl without care.
clearTextIndicatorWithAnimation(animate ? WebCore::TextIndicatorWindowDismissalAnimation::FadeOut : WebCore::TextIndicatorWindowDismissalAnimation::None);
[m_view _dismissContentRelativeChildWindows];
}
void WebViewImpl::dismissContentRelativeChildWindowsFromViewOnly()
{
bool hasActiveImmediateAction = false;
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
hasActiveImmediateAction = [m_immediateActionController hasActiveImmediateAction];
#endif
// FIXME: We don't know which panel we are dismissing, it may not even be in the current page (see <rdar://problem/13875766>).
if (m_view.window.isKeyWindow || hasActiveImmediateAction) {
WebCore::DictionaryLookup::hidePopup();
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
DDActionsManager *actionsManager = [getDDActionsManagerClass() sharedManager];
if ([actionsManager respondsToSelector:@selector(requestBubbleClosureUnanchorOnFailure:)])
[actionsManager requestBubbleClosureUnanchorOnFailure:YES];
#endif
}
clearTextIndicatorWithAnimation(WebCore::TextIndicatorWindowDismissalAnimation::FadeOut);
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
[m_immediateActionController dismissContentRelativeChildWindows];
#endif
m_pageClient.dismissCorrectionPanel(WebCore::ReasonForDismissingAlternativeTextIgnored);
}
void WebViewImpl::quickLookWithEvent(NSEvent *event)
{
if (ignoresNonWheelEvents())
return;
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
if (m_immediateActionGestureRecognizer) {
[m_view _superQuickLookWithEvent:event];
return;
}
#endif
NSPoint locationInViewCoordinates = [m_view convertPoint:[event locationInWindow] fromView:nil];
m_page.performDictionaryLookupAtLocation(WebCore::FloatPoint(locationInViewCoordinates));
}
void WebViewImpl::prepareForDictionaryLookup()
{
if (m_didRegisterForLookupPopoverCloseNotifications)
return;
m_didRegisterForLookupPopoverCloseNotifications = true;
[m_windowVisibilityObserver startObservingLookupDismissal];
}
void WebViewImpl::setAllowsLinkPreview(bool allowsLinkPreview)
{
if (m_allowsLinkPreview == allowsLinkPreview)
return;
m_allowsLinkPreview = allowsLinkPreview;
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
if (!allowsLinkPreview)
[m_view removeGestureRecognizer:m_immediateActionGestureRecognizer.get()];
else if (NSGestureRecognizer *immediateActionRecognizer = m_immediateActionGestureRecognizer.get())
[m_view addGestureRecognizer:immediateActionRecognizer];
#endif
}
void* WebViewImpl::immediateActionAnimationControllerForHitTestResult(WKHitTestResultRef hitTestResult, uint32_t type, WKTypeRef userData)
{
return [m_view _immediateActionAnimationControllerForHitTestResult:hitTestResult withType:type userData:userData];
}
void* WebViewImpl::immediateActionAnimationControllerForHitTestResultFromViewOnly(WKHitTestResultRef hitTestResult, uint32_t type, WKTypeRef userData)
{
return nil;
}
void WebViewImpl::didPerformImmediateActionHitTest(const WebHitTestResultData& result, bool contentPreventsDefault, API::Object* userData)
{
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
[m_immediateActionController didPerformImmediateActionHitTest:result contentPreventsDefault:contentPreventsDefault userData:userData];
#endif
}
void WebViewImpl::prepareForImmediateActionAnimation()
{
[m_view _prepareForImmediateActionAnimation];
}
void WebViewImpl::cancelImmediateActionAnimation()
{
[m_view _cancelImmediateActionAnimation];
}
void WebViewImpl::completeImmediateActionAnimation()
{
[m_view _completeImmediateActionAnimation];
}
void WebViewImpl::setIgnoresNonWheelEvents(bool ignoresNonWheelEvents)
{
if (m_ignoresNonWheelEvents == ignoresNonWheelEvents)
return;
m_ignoresNonWheelEvents = ignoresNonWheelEvents;
m_page.setShouldDispatchFakeMouseMoveEvents(!ignoresNonWheelEvents);
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
if (ignoresNonWheelEvents)
[m_view removeGestureRecognizer:m_immediateActionGestureRecognizer.get()];
else if (NSGestureRecognizer *immediateActionRecognizer = m_immediateActionGestureRecognizer.get()) {
if (m_allowsLinkPreview)
[m_view addGestureRecognizer:immediateActionRecognizer];
}
#endif
}
void WebViewImpl::setIgnoresAllEvents(bool ignoresAllEvents)
{
m_ignoresAllEvents = ignoresAllEvents;
setIgnoresNonWheelEvents(ignoresAllEvents);
}
void WebViewImpl::setIgnoresMouseDraggedEvents(bool ignoresMouseDraggedEvents)
{
m_ignoresMouseDraggedEvents = ignoresMouseDraggedEvents;
}
void WebViewImpl::setAccessibilityWebProcessToken(NSData *data)
{
m_remoteAccessibilityChild = WKAXRemoteElementForToken(data);
updateRemoteAccessibilityRegistration(true);
}
void WebViewImpl::updateRemoteAccessibilityRegistration(bool registerProcess)
{
// When the tree is connected/disconnected, the remote accessibility registration
// needs to be updated with the pid of the remote process. If the process is going
// away, that information is not present in WebProcess
pid_t pid = 0;
if (registerProcess)
pid = m_page.process().processIdentifier();
else if (!registerProcess) {
pid = WKAXRemoteProcessIdentifier(m_remoteAccessibilityChild.get());
m_remoteAccessibilityChild = nil;
}
if (pid)
WKAXRegisterRemoteProcess(registerProcess, pid);
}
void WebViewImpl::accessibilityRegisterUIProcessTokens()
{
// Initialize remote accessibility when the window connection has been established.
NSData *remoteElementToken = WKAXRemoteTokenForElement(m_view);
NSData *remoteWindowToken = WKAXRemoteTokenForElement(m_view.window);
IPC::DataReference elementToken = IPC::DataReference(reinterpret_cast<const uint8_t*>([remoteElementToken bytes]), [remoteElementToken length]);
IPC::DataReference windowToken = IPC::DataReference(reinterpret_cast<const uint8_t*>([remoteWindowToken bytes]), [remoteWindowToken length]);
m_page.registerUIProcessAccessibilityTokens(elementToken, windowToken);
}
id WebViewImpl::accessibilityFocusedUIElement()
{
enableAccessibilityIfNecessary();
return m_remoteAccessibilityChild.get();
}
id WebViewImpl::accessibilityHitTest(CGPoint)
{
return accessibilityFocusedUIElement();
}
void WebViewImpl::enableAccessibilityIfNecessary()
{
if (WebCore::AXObjectCache::accessibilityEnabled())
return;
// After enabling accessibility update the window frame on the web process so that the
// correct accessibility position is transmitted (when AX is off, that position is not calculated).
WebCore::AXObjectCache::enableAccessibility();
updateWindowAndViewFrames();
}
id WebViewImpl::accessibilityAttributeValue(NSString *attribute)
{
enableAccessibilityIfNecessary();
if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) {
id child = nil;
if (m_remoteAccessibilityChild)
child = m_remoteAccessibilityChild.get();
if (!child)
return nil;
return [NSArray arrayWithObject:child];
}
if ([attribute isEqualToString:NSAccessibilityRoleAttribute])
return NSAccessibilityGroupRole;
if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute])
return NSAccessibilityRoleDescription(NSAccessibilityGroupRole, nil);
if ([attribute isEqualToString:NSAccessibilityParentAttribute])
return NSAccessibilityUnignoredAncestor([m_view superview]);
if ([attribute isEqualToString:NSAccessibilityEnabledAttribute])
return @YES;
return [m_view _superAccessibilityAttributeValue:attribute];
}
void WebViewImpl::setPrimaryTrackingArea(NSTrackingArea *trackingArea)
{
[m_view removeTrackingArea:m_primaryTrackingArea.get()];
m_primaryTrackingArea = trackingArea;
[m_view addTrackingArea:trackingArea];
}
// Any non-zero value will do, but using something recognizable might help us debug some day.
#define TRACKING_RECT_TAG 0xBADFACE
NSTrackingRectTag WebViewImpl::addTrackingRect(CGRect, id owner, void* userData, bool assumeInside)
{
ASSERT(m_trackingRectOwner == nil);
m_trackingRectOwner = owner;
m_trackingRectUserData = userData;
return TRACKING_RECT_TAG;
}
NSTrackingRectTag WebViewImpl::addTrackingRectWithTrackingNum(CGRect, id owner, void* userData, bool assumeInside, int tag)
{
ASSERT(tag == 0 || tag == TRACKING_RECT_TAG);
ASSERT(m_trackingRectOwner == nil);
m_trackingRectOwner = owner;
m_trackingRectUserData = userData;
return TRACKING_RECT_TAG;
}
void WebViewImpl::addTrackingRectsWithTrackingNums(CGRect*, id owner, void** userDataList, bool assumeInside, NSTrackingRectTag *trackingNums, int count)
{
ASSERT(count == 1);
ASSERT(trackingNums[0] == 0 || trackingNums[0] == TRACKING_RECT_TAG);
ASSERT(m_trackingRectOwner == nil);
m_trackingRectOwner = owner;
m_trackingRectUserData = userDataList[0];
trackingNums[0] = TRACKING_RECT_TAG;
}
void WebViewImpl::removeTrackingRect(NSTrackingRectTag tag)
{
if (tag == 0)
return;
if (tag == TRACKING_RECT_TAG) {
m_trackingRectOwner = nil;
return;
}
if (tag == m_lastToolTipTag) {
[m_view _superRemoveTrackingRect:tag];
m_lastToolTipTag = 0;
return;
}
// If any other tracking rect is being removed, we don't know how it was created
// and it's possible there's a leak involved (see 3500217)
ASSERT_NOT_REACHED();
}
void WebViewImpl::removeTrackingRects(NSTrackingRectTag *tags, int count)
{
for (int i = 0; i < count; ++i) {
int tag = tags[i];
if (tag == 0)
continue;
ASSERT(tag == TRACKING_RECT_TAG);
m_trackingRectOwner = nil;
}
}
void WebViewImpl::sendToolTipMouseExited()
{
// Nothing matters except window, trackingNumber, and userData.
NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited
location:NSMakePoint(0, 0)
modifierFlags:0
timestamp:0
windowNumber:m_view.window.windowNumber
context:NULL
eventNumber:0
trackingNumber:TRACKING_RECT_TAG
userData:m_trackingRectUserData];
[m_trackingRectOwner mouseExited:fakeEvent];
}
void WebViewImpl::sendToolTipMouseEntered()
{
// Nothing matters except window, trackingNumber, and userData.
NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered
location:NSMakePoint(0, 0)
modifierFlags:0
timestamp:0
windowNumber:m_view.window.windowNumber
context:NULL
eventNumber:0
trackingNumber:TRACKING_RECT_TAG
userData:m_trackingRectUserData];
[m_trackingRectOwner mouseEntered:fakeEvent];
}
NSString *WebViewImpl::stringForToolTip(NSToolTipTag tag)
{
return nsStringFromWebCoreString(m_page.toolTip());
}
void WebViewImpl::toolTipChanged(const String& oldToolTip, const String& newToolTip)
{
if (!oldToolTip.isNull())
sendToolTipMouseExited();
if (!newToolTip.isEmpty()) {
// See radar 3500217 for why we remove all tooltips rather than just the single one we created.
[m_view removeAllToolTips];
NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000);
m_lastToolTipTag = [m_view addToolTipRect:wideOpenRect owner:m_view userData:NULL];
sendToolTipMouseEntered();
}
}
void WebViewImpl::setAcceleratedCompositingRootLayer(CALayer *rootLayer)
{
[rootLayer web_disableAllActions];
m_rootLayer = rootLayer;
#if WK_API_ENABLED
if (m_thumbnailView) {
updateThumbnailViewLayer();
return;
}
#endif
[CATransaction begin];
[CATransaction setDisableActions:YES];
if (rootLayer) {
if (!m_layerHostingView) {
// Create an NSView that will host our layer tree.
m_layerHostingView = adoptNS([[WKFlippedView alloc] initWithFrame:m_view.bounds]);
[m_layerHostingView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
[m_view addSubview:m_layerHostingView.get() positioned:NSWindowBelow relativeTo:nil];
// Create a root layer that will back the NSView.
RetainPtr<CALayer> layer = adoptNS([[CALayer alloc] init]);
[layer setDelegate:[WebActionDisablingCALayerDelegate shared]];
#ifndef NDEBUG
[layer setName:@"Hosting root layer"];
#endif
[m_layerHostingView setLayer:layer.get()];
[m_layerHostingView setWantsLayer:YES];
}
[m_layerHostingView layer].sublayers = [NSArray arrayWithObject:rootLayer];
} else if (m_layerHostingView) {
[m_layerHostingView removeFromSuperview];
[m_layerHostingView setLayer:nil];
[m_layerHostingView setWantsLayer:NO];
m_layerHostingView = nullptr;
}
[CATransaction commit];
}
#if WK_API_ENABLED
void WebViewImpl::setThumbnailView(_WKThumbnailView *thumbnailView)
{
ASSERT(!m_thumbnailView || !thumbnailView);
m_thumbnailView = thumbnailView;
if (thumbnailView)
updateThumbnailViewLayer();
else
setAcceleratedCompositingRootLayer(m_rootLayer.get());
}
void WebViewImpl::reparentLayerTreeInThumbnailView()
{
m_thumbnailView._thumbnailLayer = m_rootLayer.get();
}
void WebViewImpl::updateThumbnailViewLayer()
{
_WKThumbnailView *thumbnailView = m_thumbnailView;
ASSERT(thumbnailView);
if (thumbnailView._waitingForSnapshot && m_view.window)
reparentLayerTreeInThumbnailView();
}
#endif // WK_API_ENABLED
#if ENABLE(DRAG_SUPPORT)
void WebViewImpl::draggedImage(NSImage *image, CGPoint endPoint, NSDragOperation operation)
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
NSPoint windowImageLoc = [m_view.window convertScreenToBase:NSPointFromCGPoint(endPoint)];
#pragma clang diagnostic pop
NSPoint windowMouseLoc = windowImageLoc;
// Prevent queued mouseDragged events from coming after the drag and fake mouseUp event.
m_ignoresMouseDraggedEvents = true;
m_page.dragEnded(WebCore::IntPoint(windowMouseLoc), WebCore::globalPoint(windowMouseLoc, m_view.window), operation);
}
static WebCore::DragApplicationFlags applicationFlagsForDrag(NSView *view, id <NSDraggingInfo> draggingInfo)
{
uint32_t flags = 0;
if ([NSApp modalWindow])
flags = WebCore::DragApplicationIsModal;
if (view.window.attachedSheet)
flags |= WebCore::DragApplicationHasAttachedSheet;
if (draggingInfo.draggingSource == view)
flags |= WebCore::DragApplicationIsSource;
if ([NSApp currentEvent].modifierFlags & NSAlternateKeyMask)
flags |= WebCore::DragApplicationIsCopyKeyDown;
return static_cast<WebCore::DragApplicationFlags>(flags);
}
NSDragOperation WebViewImpl::draggingEntered(id <NSDraggingInfo> draggingInfo)
{
WebCore::IntPoint client([m_view convertPoint:draggingInfo.draggingLocation fromView:nil]);
WebCore::IntPoint global(WebCore::globalPoint(draggingInfo.draggingLocation, m_view.window));
WebCore::DragData dragData(draggingInfo, client, global, static_cast<WebCore::DragOperation>(draggingInfo.draggingSourceOperationMask), applicationFlagsForDrag(m_view, draggingInfo));
m_page.resetCurrentDragInformation();
m_page.dragEntered(dragData, draggingInfo.draggingPasteboard.name);
return NSDragOperationCopy;
}
NSDragOperation WebViewImpl::draggingUpdated(id <NSDraggingInfo> draggingInfo)
{
WebCore::IntPoint client([m_view convertPoint:draggingInfo.draggingLocation fromView:nil]);
WebCore::IntPoint global(WebCore::globalPoint(draggingInfo.draggingLocation, m_view.window));
WebCore::DragData dragData(draggingInfo, client, global, static_cast<WebCore::DragOperation>(draggingInfo.draggingSourceOperationMask), applicationFlagsForDrag(m_view, draggingInfo));
m_page.dragUpdated(dragData, draggingInfo.draggingPasteboard.name);
NSInteger numberOfValidItemsForDrop = m_page.currentDragNumberOfFilesToBeAccepted();
NSDraggingFormation draggingFormation = NSDraggingFormationNone;
if (m_page.currentDragIsOverFileInput() && numberOfValidItemsForDrop > 0)
draggingFormation = NSDraggingFormationList;
if (draggingInfo.numberOfValidItemsForDrop != numberOfValidItemsForDrop)
[draggingInfo setNumberOfValidItemsForDrop:numberOfValidItemsForDrop];
if (draggingInfo.draggingFormation != draggingFormation)
[draggingInfo setDraggingFormation:draggingFormation];
return m_page.currentDragOperation();
}
void WebViewImpl::draggingExited(id <NSDraggingInfo> draggingInfo)
{
WebCore::IntPoint client([m_view convertPoint:draggingInfo.draggingLocation fromView:nil]);
WebCore::IntPoint global(WebCore::globalPoint(draggingInfo.draggingLocation, m_view.window));
WebCore::DragData dragData(draggingInfo, client, global, static_cast<WebCore::DragOperation>(draggingInfo.draggingSourceOperationMask), applicationFlagsForDrag(m_view, draggingInfo));
m_page.dragExited(dragData, draggingInfo.draggingPasteboard.name);
m_page.resetCurrentDragInformation();
}
bool WebViewImpl::prepareForDragOperation(id <NSDraggingInfo>)
{
return true;
}
// FIXME: This code is more or less copied from Pasteboard::getBestURL.
// It would be nice to be able to share the code somehow.
static bool maybeCreateSandboxExtensionFromPasteboard(NSPasteboard *pasteboard, SandboxExtension::Handle& sandboxExtensionHandle)
{
NSArray *types = pasteboard.types;
if (![types containsObject:NSFilenamesPboardType])
return false;
NSArray *files = [pasteboard propertyListForType:NSFilenamesPboardType];
if (files.count != 1)
return false;
NSString *file = [files objectAtIndex:0];
BOOL isDirectory;
if (![[NSFileManager defaultManager] fileExistsAtPath:file isDirectory:&isDirectory])
return false;
if (isDirectory)
return false;
SandboxExtension::createHandle("/", SandboxExtension::ReadOnly, sandboxExtensionHandle);
return true;
}
static void createSandboxExtensionsForFileUpload(NSPasteboard *pasteboard, SandboxExtension::HandleArray& handles)
{
NSArray *types = pasteboard.types;
if (![types containsObject:NSFilenamesPboardType])
return;
NSArray *files = [pasteboard propertyListForType:NSFilenamesPboardType];
handles.allocate(files.count);
for (unsigned i = 0; i < files.count; i++) {
NSString *file = [files objectAtIndex:i];
if (![[NSFileManager defaultManager] fileExistsAtPath:file])
continue;
SandboxExtension::Handle handle;
SandboxExtension::createHandle(file, SandboxExtension::ReadOnly, handles[i]);
}
}
bool WebViewImpl::performDragOperation(id <NSDraggingInfo> draggingInfo)
{
WebCore::IntPoint client([m_view convertPoint:draggingInfo.draggingLocation fromView:nil]);
WebCore::IntPoint global(WebCore::globalPoint(draggingInfo.draggingLocation, m_view.window));
WebCore::DragData dragData(draggingInfo, client, global, static_cast<WebCore::DragOperation>(draggingInfo.draggingSourceOperationMask), applicationFlagsForDrag(m_view, draggingInfo));
SandboxExtension::Handle sandboxExtensionHandle;
bool createdExtension = maybeCreateSandboxExtensionFromPasteboard(draggingInfo.draggingPasteboard, sandboxExtensionHandle);
if (createdExtension)
m_page.process().willAcquireUniversalFileReadSandboxExtension();
SandboxExtension::HandleArray sandboxExtensionForUpload;
createSandboxExtensionsForFileUpload(draggingInfo.draggingPasteboard, sandboxExtensionForUpload);
m_page.performDragOperation(dragData, draggingInfo.draggingPasteboard.name, sandboxExtensionHandle, sandboxExtensionForUpload);
return true;
}
NSView *WebViewImpl::hitTestForDragTypes(CGPoint point, NSSet *types)
{
// This code is needed to support drag and drop when the drag types cannot be matched.
// This is the case for elements that do not place content
// in the drag pasteboard automatically when the drag start (i.e. dragging a DIV element).
if ([[m_view superview] mouse:NSPointFromCGPoint(point) inRect:[m_view frame]])
return m_view;
return nil;
}
void WebViewImpl::registerDraggedTypes()
{
auto types = adoptNS([[NSMutableSet alloc] initWithArray:PasteboardTypes::forEditing()]);
[types addObjectsFromArray:PasteboardTypes::forURL()];
[m_view registerForDraggedTypes:[types allObjects]];
}
#endif // ENABLE(DRAG_SUPPORT)
static RetainPtr<CGImageRef> takeWindowSnapshot(CGSWindowID windowID, bool captureAtNominalResolution)
{
CGSWindowCaptureOptions options = kCGSCaptureIgnoreGlobalClipShape;
if (captureAtNominalResolution)
options |= kCGSWindowCaptureNominalResolution;
RetainPtr<CFArrayRef> windowSnapshotImages = adoptCF(CGSHWCaptureWindowList(CGSMainConnectionID(), &windowID, 1, options));
if (windowSnapshotImages && CFArrayGetCount(windowSnapshotImages.get()))
return (CGImageRef)CFArrayGetValueAtIndex(windowSnapshotImages.get(), 0);
// Fall back to the non-hardware capture path if we didn't get a snapshot
// (which usually happens if the window is fully off-screen).
CGWindowImageOption imageOptions = kCGWindowImageBoundsIgnoreFraming | kCGWindowImageShouldBeOpaque;
if (captureAtNominalResolution)
imageOptions |= kCGWindowImageNominalResolution;
return adoptCF(CGWindowListCreateImage(CGRectNull, kCGWindowListOptionIncludingWindow, windowID, imageOptions));
}
RefPtr<ViewSnapshot> WebViewImpl::takeViewSnapshot()
{
NSWindow *window = m_view.window;
CGSWindowID windowID = (CGSWindowID)window.windowNumber;
if (!windowID || !window.isVisible)
return nullptr;
RetainPtr<CGImageRef> windowSnapshotImage = takeWindowSnapshot(windowID, false);
if (!windowSnapshotImage)
return nullptr;
// Work around <rdar://problem/17084993>; re-request the snapshot at kCGWindowImageNominalResolution if it was captured at the wrong scale.
CGFloat desiredSnapshotWidth = window.frame.size.width * window.screen.backingScaleFactor;
if (CGImageGetWidth(windowSnapshotImage.get()) != desiredSnapshotWidth)
windowSnapshotImage = takeWindowSnapshot(windowID, true);
if (!windowSnapshotImage)
return nullptr;
ViewGestureController& gestureController = ensureGestureController();
NSRect windowCaptureRect;
WebCore::FloatRect boundsForCustomSwipeViews = gestureController.windowRelativeBoundsForCustomSwipeViews();
if (!boundsForCustomSwipeViews.isEmpty())
windowCaptureRect = boundsForCustomSwipeViews;
else {
NSRect unobscuredBounds = m_view.bounds;
float topContentInset = m_page.topContentInset();
unobscuredBounds.origin.y += topContentInset;
unobscuredBounds.size.height -= topContentInset;
windowCaptureRect = [m_view convertRect:unobscuredBounds toView:nil];
}
NSRect windowCaptureScreenRect = [window convertRectToScreen:windowCaptureRect];
CGRect windowScreenRect;
CGSGetScreenRectForWindow(CGSMainConnectionID(), (CGSWindowID)[window windowNumber], &windowScreenRect);
NSRect croppedImageRect = windowCaptureRect;
croppedImageRect.origin.y = windowScreenRect.size.height - windowCaptureScreenRect.size.height - NSMinY(windowCaptureRect);
auto croppedSnapshotImage = adoptCF(CGImageCreateWithImageInRect(windowSnapshotImage.get(), NSRectToCGRect([window convertRectToBacking:croppedImageRect])));
auto surface = WebCore::IOSurface::createFromImage(croppedSnapshotImage.get());
if (!surface)
return nullptr;
surface->setIsVolatile(true);
return ViewSnapshot::create(WTF::move(surface));
}
ViewGestureController& WebViewImpl::ensureGestureController()
{
if (!m_gestureController)
m_gestureController = std::make_unique<ViewGestureController>(m_page);
return *m_gestureController;
}
void WebViewImpl::resetGestureController()
{
m_gestureController = nullptr;
}
void WebViewImpl::setAllowsBackForwardNavigationGestures(bool allowsBackForwardNavigationGestures)
{
m_allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures;
m_page.setShouldRecordNavigationSnapshots(allowsBackForwardNavigationGestures);
m_page.setShouldUseImplicitRubberBandControl(allowsBackForwardNavigationGestures);
}
void WebViewImpl::setAllowsMagnification(bool allowsMagnification)
{
m_allowsMagnification = allowsMagnification;
}
void WebViewImpl::setMagnification(double magnification, CGPoint centerPoint)
{
if (magnification <= 0 || isnan(magnification) || isinf(magnification))
[NSException raise:NSInvalidArgumentException format:@"Magnification should be a positive number"];
dismissContentRelativeChildWindowsWithAnimation(false);
m_page.scalePageInViewCoordinates(magnification, WebCore::roundedIntPoint(centerPoint));
}
void WebViewImpl::setMagnification(double magnification)
{
if (magnification <= 0 || isnan(magnification) || isinf(magnification))
[NSException raise:NSInvalidArgumentException format:@"Magnification should be a positive number"];
dismissContentRelativeChildWindowsWithAnimation(false);
WebCore::FloatPoint viewCenter(NSMidX([m_view bounds]), NSMidY([m_view bounds]));
m_page.scalePageInViewCoordinates(magnification, roundedIntPoint(viewCenter));
}
double WebViewImpl::magnification() const
{
if (m_gestureController)
return m_gestureController->magnification();
return m_page.pageScaleFactor();
}
void WebViewImpl::setCustomSwipeViews(NSArray *customSwipeViews)
{
if (!customSwipeViews.count && !m_gestureController)
return;
Vector<RetainPtr<NSView>> views;
views.reserveInitialCapacity(customSwipeViews.count);
for (NSView *view in customSwipeViews)
views.uncheckedAppend(view);
ensureGestureController().setCustomSwipeViews(views);
}
void WebViewImpl::setCustomSwipeViewsTopContentInset(float topContentInset)
{
ensureGestureController().setCustomSwipeViewsTopContentInset(topContentInset);
}
bool WebViewImpl::tryToSwipeWithEvent(NSEvent *event, bool ignoringPinnedState)
{
if (!m_allowsBackForwardNavigationGestures)
return false;
auto& gestureController = ensureGestureController();
bool wasIgnoringPinnedState = gestureController.shouldIgnorePinnedState();
gestureController.setShouldIgnorePinnedState(ignoringPinnedState);
bool handledEvent = gestureController.handleScrollWheelEvent(event);
gestureController.setShouldIgnorePinnedState(wasIgnoringPinnedState);
return handledEvent;
}
void WebViewImpl::setDidMoveSwipeSnapshotCallback(void(^callback)(CGRect))
{
if (!m_allowsBackForwardNavigationGestures)
return;
ensureGestureController().setDidMoveSwipeSnapshotCallback(callback);
}
void WebViewImpl::scrollWheel(NSEvent *event)
{
if (m_ignoresAllEvents)
return;
if (event.phase == NSEventPhaseBegan)
dismissContentRelativeChildWindowsWithAnimation(false);
if (m_allowsBackForwardNavigationGestures && ensureGestureController().handleScrollWheelEvent(event))
return;
NativeWebWheelEvent webEvent = NativeWebWheelEvent(event, m_view);
m_page.handleWheelEvent(webEvent);
}
void WebViewImpl::swipeWithEvent(NSEvent *event)
{
if (m_ignoresNonWheelEvents)
return;
if (!m_allowsBackForwardNavigationGestures) {
[m_view _superSwipeWithEvent:event];
return;
}
if (event.deltaX > 0.0)
m_page.goBack();
else if (event.deltaX < 0.0)
m_page.goForward();
else
[m_view _superSwipeWithEvent:event];
}
void WebViewImpl::magnifyWithEvent(NSEvent *event)
{
if (!m_allowsMagnification) {
#if ENABLE(MAC_GESTURE_EVENTS)
NativeWebGestureEvent webEvent = NativeWebGestureEvent(event, m_view);
m_page.handleGestureEvent(webEvent);
#endif
[m_view _superMagnifyWithEvent:event];
return;
}
dismissContentRelativeChildWindowsWithAnimation(false);
auto& gestureController = ensureGestureController();
#if ENABLE(MAC_GESTURE_EVENTS)
if (gestureController.hasActiveMagnificationGesture()) {
gestureController.handleMagnificationGestureEvent(event, [m_view convertPoint:event.locationInWindow fromView:nil]);
return;
}
NativeWebGestureEvent webEvent = NativeWebGestureEvent(event, m_view);
m_page.handleGestureEvent(webEvent);
#else
gestureController.handleMagnificationGestureEvent(event, [m_view convertPoint:event.locationInWindow fromView:nil]);
#endif
}
void WebViewImpl::smartMagnifyWithEvent(NSEvent *event)
{
if (!m_allowsMagnification) {
[m_view _superSmartMagnifyWithEvent:event];
return;
}
dismissContentRelativeChildWindowsWithAnimation(false);
ensureGestureController().handleSmartMagnificationGesture([m_view convertPoint:event.locationInWindow fromView:nil]);
}
#if ENABLE(MAC_GESTURE_EVENTS)
void WebViewImpl::rotateWithEvent(NSEvent *event)
{
NativeWebGestureEvent webEvent = NativeWebGestureEvent(event, m_view);
m_page.handleGestureEvent(webEvent);
}
#endif
void WebViewImpl::gestureEventWasNotHandledByWebCore(NSEvent *event)
{
[m_view _gestureEventWasNotHandledByWebCore:event];
}
void WebViewImpl::gestureEventWasNotHandledByWebCoreFromViewOnly(NSEvent *event)
{
#if ENABLE(MAC_GESTURE_EVENTS)
if (m_gestureController)
m_gestureController->gestureEventWasNotHandledByWebCore(event, [m_view convertPoint:event.locationInWindow fromView:nil]);
#endif
}
} // namespace WebKit
#endif // PLATFORM(MAC)