| /* |
| * Copyright (C) 2015-2019 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 "APIAttachment.h" |
| #import "APILegacyContextHistoryClient.h" |
| #import "APINavigation.h" |
| #import "AppKitSPI.h" |
| #import "AttributedString.h" |
| #import "ColorSpaceData.h" |
| #import "FontInfo.h" |
| #import "FullscreenClient.h" |
| #import "GenericCallback.h" |
| #import "InsertTextOptions.h" |
| #import "Logging.h" |
| #import "NativeWebGestureEvent.h" |
| #import "NativeWebKeyboardEvent.h" |
| #import "NativeWebMouseEvent.h" |
| #import "NativeWebWheelEvent.h" |
| #import "PageClient.h" |
| #import "PageClientImplMac.h" |
| #import "PasteboardTypes.h" |
| #import "PlaybackSessionManagerProxy.h" |
| #import "RemoteLayerTreeDrawingAreaProxy.h" |
| #import "RemoteObjectRegistry.h" |
| #import "RemoteObjectRegistryMessages.h" |
| #import "StringUtilities.h" |
| #import "TextChecker.h" |
| #import "TextCheckerState.h" |
| #import "TiledCoreAnimationDrawingAreaProxy.h" |
| #import "UIGamepadProvider.h" |
| #import "UndoOrRedo.h" |
| #import "ViewGestureController.h" |
| #import "WKBrowsingContextControllerInternal.h" |
| #import "WKEditCommand.h" |
| #import "WKErrorInternal.h" |
| #import "WKFullScreenWindowController.h" |
| #import "WKImmediateActionController.h" |
| #import "WKPrintingView.h" |
| #import "WKSafeBrowsingWarning.h" |
| #import "WKShareSheet.h" |
| #import "WKTextInputWindowController.h" |
| #import "WKViewLayoutStrategy.h" |
| #import "WKWebViewInternal.h" |
| #import "WKWebViewPrivate.h" |
| #import "WebBackForwardList.h" |
| #import "WebEditCommandProxy.h" |
| #import "WebEventFactory.h" |
| #import "WebInspectorProxy.h" |
| #import "WebPageProxy.h" |
| #import "WebProcessPool.h" |
| #import "WebProcessProxy.h" |
| #import "_WKRemoteObjectRegistryInternal.h" |
| #import "_WKThumbnailViewInternal.h" |
| #import <Carbon/Carbon.h> |
| #import <WebCore/AXObjectCache.h> |
| #import <WebCore/ActivityState.h> |
| #import <WebCore/ColorMac.h> |
| #import <WebCore/DictionaryLookup.h> |
| #import <WebCore/DragData.h> |
| #import <WebCore/DragItem.h> |
| #import <WebCore/Editor.h> |
| #import <WebCore/FontAttributeChanges.h> |
| #import <WebCore/FontAttributes.h> |
| #import <WebCore/KeypressCommand.h> |
| #import <WebCore/LegacyNSPasteboardTypes.h> |
| #import <WebCore/LoaderNSURLExtras.h> |
| #import <WebCore/LocalizedStrings.h> |
| #import <WebCore/Pasteboard.h> |
| #import <WebCore/PlatformEventFactoryMac.h> |
| #import <WebCore/PromisedAttachmentInfo.h> |
| #import <WebCore/TextAlternativeWithRange.h> |
| #import <WebCore/TextUndoInsertionMarkupMac.h> |
| #import <WebCore/WebActionDisablingCALayerDelegate.h> |
| #import <WebCore/WebCoreCALayerExtras.h> |
| #import <WebCore/WebCoreFullScreenPlaceholderView.h> |
| #import <WebCore/WebCoreFullScreenWindow.h> |
| #import <WebCore/WebCoreNSFontManagerExtras.h> |
| #import <WebCore/WebPlaybackControlsManager.h> |
| #import <pal/spi/cg/CoreGraphicsSPI.h> |
| #import <pal/spi/cocoa/AVKitSPI.h> |
| #import <pal/spi/cocoa/NSTouchBarSPI.h> |
| #import <pal/spi/mac/DataDetectorsSPI.h> |
| #import <pal/spi/mac/LookupSPI.h> |
| #import <pal/spi/mac/NSAccessibilitySPI.h> |
| #import <pal/spi/mac/NSApplicationSPI.h> |
| #import <pal/spi/mac/NSImmediateActionGestureRecognizerSPI.h> |
| #import <pal/spi/mac/NSScrollerImpSPI.h> |
| #import <pal/spi/mac/NSSpellCheckerSPI.h> |
| #import <pal/spi/mac/NSTextFinderSPI.h> |
| #import <pal/spi/mac/NSViewSPI.h> |
| #import <pal/spi/mac/NSWindowSPI.h> |
| #import <sys/stat.h> |
| #import <wtf/FileSystem.h> |
| #import <wtf/NeverDestroyed.h> |
| #import <wtf/ProcessPrivilege.h> |
| #import <wtf/SetForScope.h> |
| #import <wtf/SoftLinking.h> |
| #import <wtf/cf/TypeCastsCF.h> |
| #import <wtf/text/StringConcatenate.h> |
| |
| #if HAVE(TOUCH_BAR) && ENABLE(WEB_PLAYBACK_CONTROLS_MANAGER) |
| SOFT_LINK_FRAMEWORK(AVKit) |
| SOFT_LINK_CLASS(AVKit, AVTouchBarPlaybackControlsProvider) |
| SOFT_LINK_CLASS(AVKit, AVTouchBarScrubber) |
| |
| static NSString * const WKMediaExitFullScreenItem = @"WKMediaExitFullScreenItem"; |
| #endif // HAVE(TOUCH_BAR) && ENABLE(WEB_PLAYBACK_CONTROLS_MANAGER) |
| |
| WTF_DECLARE_CF_TYPE_TRAIT(CGImage); |
| |
| @interface NSApplication () |
| - (BOOL)isSpeaking; |
| - (void)speakString:(NSString *)string; |
| - (void)stopSpeaking:(id)sender; |
| @end |
| |
| // FIXME: Move to an SPI header. |
| @interface NSTextInputContext (WKNSTextInputContextDetails) |
| - (void)handleEvent:(NSEvent *)event completionHandler:(void(^)(BOOL handled))completionHandler; |
| - (void)handleEventByInputMethod:(NSEvent *)event completionHandler:(void(^)(BOOL handled))completionHandler; |
| - (BOOL)handleEventByKeyboardLayout:(NSEvent *)event; |
| @end |
| |
| #if HAVE(TOUCH_BAR) && ENABLE(WEB_PLAYBACK_CONTROLS_MANAGER) |
| // FIXME: Remove this once -setCanShowMediaSelectionButton: is declared in an SDK used by Apple's buildbot. |
| @interface AVTouchBarScrubber () |
| - (void)setCanShowMediaSelectionButton:(BOOL)canShowMediaSelectionButton; |
| @end |
| #endif |
| |
| @interface WKAccessibilitySettingsObserver : NSObject { |
| WebKit::WebViewImpl *_impl; |
| } |
| |
| - (instancetype)initWithImpl:(WebKit::WebViewImpl&)impl; |
| @end |
| |
| @implementation WKAccessibilitySettingsObserver |
| |
| - (instancetype)initWithImpl:(WebKit::WebViewImpl&)impl |
| { |
| self = [super init]; |
| if (!self) |
| return nil; |
| |
| _impl = &impl; |
| |
| NSNotificationCenter* workspaceNotificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter]; |
| [workspaceNotificationCenter addObserver:self selector:@selector(_settingsDidChange:) name:NSWorkspaceAccessibilityDisplayOptionsDidChangeNotification object:nil]; |
| |
| return self; |
| } |
| |
| - (void)dealloc |
| { |
| NSNotificationCenter *workspaceNotificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter]; |
| [workspaceNotificationCenter removeObserver:self name:NSWorkspaceAccessibilityDisplayOptionsDidChangeNotification object:nil]; |
| |
| [super dealloc]; |
| } |
| |
| - (void)_settingsDidChange:(NSNotification *)notification |
| { |
| _impl->accessibilitySettingsDidChange(); |
| } |
| |
| @end |
| |
| @interface WKWindowVisibilityObserver : NSObject { |
| NSView *_view; |
| WebKit::WebViewImpl *_impl; |
| |
| BOOL _didRegisterForLookupPopoverCloseNotifications; |
| } |
| |
| - (instancetype)initWithView:(NSView *)view impl:(WebKit::WebViewImpl&)impl; |
| - (void)startObserving:(NSWindow *)window; |
| - (void)stopObserving:(NSWindow *)window; |
| - (void)startObservingFontPanel; |
| - (void)startObservingLookupDismissalIfNeeded; |
| @end |
| |
| @implementation WKWindowVisibilityObserver |
| |
| - (instancetype)initWithView:(NSView *)view impl:(WebKit::WebViewImpl&)impl |
| { |
| self = [super init]; |
| if (!self) |
| return nil; |
| |
| _view = view; |
| _impl = &impl; |
| |
| NSNotificationCenter* workspaceNotificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter]; |
| [workspaceNotificationCenter addObserver:self selector:@selector(_activeSpaceDidChange:) name:NSWorkspaceActiveSpaceDidChangeNotification object:nil]; |
| |
| return self; |
| } |
| |
| - (void)dealloc |
| { |
| #if !ENABLE(REVEAL) |
| if (_didRegisterForLookupPopoverCloseNotifications && PAL::canLoad_Lookup_LUNotificationPopoverWillClose()) |
| [[NSNotificationCenter defaultCenter] removeObserver:self name:PAL::get_Lookup_LUNotificationPopoverWillClose() object:nil]; |
| #endif // !ENABLE(REVEAL) |
| |
| NSNotificationCenter *workspaceNotificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter]; |
| [workspaceNotificationCenter removeObserver:self name:NSWorkspaceActiveSpaceDidChangeNotification object:nil]; |
| |
| [super dealloc]; |
| } |
| |
| static void* keyValueObservingContext = &keyValueObservingContext; |
| |
| - (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]; |
| |
| [window addObserver:self forKeyPath:@"contentLayoutRect" options:NSKeyValueObservingOptionInitial context:keyValueObservingContext]; |
| [window addObserver:self forKeyPath:@"titlebarAppearsTransparent" options:NSKeyValueObservingOptionInitial context:keyValueObservingContext]; |
| } |
| |
| - (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 (_impl->isEditable()) |
| [[NSFontPanel sharedFontPanel] removeObserver:self forKeyPath:@"visible" context:keyValueObservingContext]; |
| [window removeObserver:self forKeyPath:@"contentLayoutRect" context:keyValueObservingContext]; |
| [window removeObserver:self forKeyPath:@"titlebarAppearsTransparent" context:keyValueObservingContext]; |
| } |
| |
| - (void)startObservingFontPanel |
| { |
| [[NSFontPanel sharedFontPanel] addObserver:self forKeyPath:@"visible" options:0 context:keyValueObservingContext]; |
| } |
| |
| - (void)startObservingLookupDismissalIfNeeded |
| { |
| if (_didRegisterForLookupPopoverCloseNotifications) |
| return; |
| |
| _didRegisterForLookupPopoverCloseNotifications = YES; |
| #if !ENABLE(REVEAL) |
| if (PAL::canLoad_Lookup_LUNotificationPopoverWillClose()) |
| [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_dictionaryLookupPopoverWillClose:) name:PAL::get_Lookup_LUNotificationPopoverWillClose() object:nil]; |
| #endif |
| } |
| |
| - (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 (context != keyValueObservingContext) { |
| [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; |
| return; |
| } |
| |
| if ([keyPath isEqualToString:@"visible"] && [NSFontPanel sharedFontPanelExists] && object == [NSFontPanel sharedFontPanel]) { |
| _impl->updateFontManagerIfNeeded(); |
| return; |
| } |
| if ([keyPath isEqualToString:@"contentLayoutRect"] || [keyPath isEqualToString:@"titlebarAppearsTransparent"]) |
| _impl->updateContentInsetsIfAutomatic(); |
| } |
| |
| #if !ENABLE(REVEAL) |
| - (void)_dictionaryLookupPopoverWillClose:(NSNotification *)notification |
| { |
| _impl->clearTextIndicatorWithAnimation(WebCore::TextIndicatorWindowDismissalAnimation::None); |
| } |
| #endif |
| |
| - (void)_activeSpaceDidChange:(NSNotification *)notification |
| { |
| _impl->activeSpaceDidChange(); |
| } |
| |
| @end |
| |
| @interface WKFlippedView : NSView |
| @end |
| |
| @implementation WKFlippedView |
| |
| - (BOOL)isFlipped |
| { |
| return YES; |
| } |
| |
| @end |
| |
| @interface WKResponderChainSink : NSResponder { |
| NSResponder *_lastResponderInChain; |
| bool _didReceiveUnhandledCommand; |
| } |
| |
| - (id)initWithResponderChain:(NSResponder *)chain; |
| - (void)detach; |
| - (bool)didReceiveUnhandledCommand; |
| @end |
| |
| @implementation WKResponderChainSink |
| |
| - (id)initWithResponderChain:(NSResponder *)chain |
| { |
| self = [super init]; |
| if (!self) |
| return nil; |
| _lastResponderInChain = chain; |
| while (NSResponder *next = [_lastResponderInChain nextResponder]) |
| _lastResponderInChain = next; |
| [_lastResponderInChain setNextResponder:self]; |
| return self; |
| } |
| |
| - (void)detach |
| { |
| // This assumes that the responder chain was either unmodified since |
| // -initWithResponderChain: was called, or was modified in such a way |
| // that _lastResponderInChain is still in the chain, and self was not |
| // moved earlier in the chain than _lastResponderInChain. |
| NSResponder *responderBeforeSelf = _lastResponderInChain; |
| NSResponder *next = [responderBeforeSelf nextResponder]; |
| for (; next && next != self; next = [next nextResponder]) |
| responderBeforeSelf = next; |
| |
| // Nothing to be done if we are no longer in the responder chain. |
| if (next != self) |
| return; |
| |
| [responderBeforeSelf setNextResponder:[self nextResponder]]; |
| _lastResponderInChain = nil; |
| } |
| |
| - (bool)didReceiveUnhandledCommand |
| { |
| return _didReceiveUnhandledCommand; |
| } |
| |
| - (void)noResponderFor:(SEL)selector |
| { |
| _didReceiveUnhandledCommand = true; |
| } |
| |
| - (void)doCommandBySelector:(SEL)selector |
| { |
| _didReceiveUnhandledCommand = true; |
| } |
| |
| - (BOOL)tryToPerform:(SEL)action with:(id)object |
| { |
| _didReceiveUnhandledCommand = true; |
| return YES; |
| } |
| |
| @end |
| |
| #if HAVE(TOUCH_BAR) |
| |
| @interface WKTextListTouchBarViewController : NSViewController { |
| @private |
| WebKit::WebViewImpl* _webViewImpl; |
| WebKit::ListType _currentListType; |
| } |
| |
| @property (nonatomic) WebKit::ListType currentListType; |
| |
| - (instancetype)initWithWebViewImpl:(WebKit::WebViewImpl*)webViewImpl; |
| |
| @end |
| |
| @implementation WKTextListTouchBarViewController |
| |
| @synthesize currentListType=_currentListType; |
| |
| static const CGFloat listControlSegmentWidth = 67; |
| #if ENABLE(WEB_PLAYBACK_CONTROLS_MANAGER) && ENABLE(FULLSCREEN_API) |
| static const CGFloat exitFullScreenButtonWidth = 64; |
| #endif |
| |
| static const NSUInteger noListSegment = 0; |
| static const NSUInteger unorderedListSegment = 1; |
| static const NSUInteger orderedListSegment = 2; |
| |
| - (instancetype)initWithWebViewImpl:(WebKit::WebViewImpl*)webViewImpl |
| { |
| if (!(self = [super init])) |
| return nil; |
| |
| _webViewImpl = webViewImpl; |
| |
| NSSegmentedControl *insertListControl = [NSSegmentedControl segmentedControlWithLabels:@[ WebCore::insertListTypeNone(), WebCore::insertListTypeBulleted(), WebCore::insertListTypeNumbered() ] trackingMode:NSSegmentSwitchTrackingSelectOne target:self action:@selector(_selectList:)]; |
| [insertListControl setWidth:listControlSegmentWidth forSegment:noListSegment]; |
| [insertListControl setWidth:listControlSegmentWidth forSegment:unorderedListSegment]; |
| [insertListControl setWidth:listControlSegmentWidth forSegment:orderedListSegment]; |
| insertListControl.font = [NSFont systemFontOfSize:15]; |
| |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| id segmentElement = NSAccessibilityUnignoredDescendant(insertListControl); |
| NSArray *segments = [segmentElement accessibilityAttributeValue:NSAccessibilityChildrenAttribute]; |
| ASSERT(segments.count == 3); |
| [segments[noListSegment] accessibilitySetOverrideValue:WebCore::insertListTypeNone() forAttribute:NSAccessibilityDescriptionAttribute]; |
| [segments[unorderedListSegment] accessibilitySetOverrideValue:WebCore::insertListTypeBulletedAccessibilityTitle() forAttribute:NSAccessibilityDescriptionAttribute]; |
| [segments[orderedListSegment] accessibilitySetOverrideValue:WebCore::insertListTypeNumberedAccessibilityTitle() forAttribute:NSAccessibilityDescriptionAttribute]; |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| |
| self.view = insertListControl; |
| |
| return self; |
| } |
| |
| - (void)didDestroyView |
| { |
| _webViewImpl = nullptr; |
| } |
| |
| - (void)_selectList:(id)sender |
| { |
| if (!_webViewImpl) |
| return; |
| |
| NSSegmentedControl *insertListControl = (NSSegmentedControl *)self.view; |
| switch (insertListControl.selectedSegment) { |
| case noListSegment: |
| // There is no "remove list" edit command, but InsertOrderedList and InsertUnorderedList both |
| // behave as toggles, so we can invoke the appropriate edit command depending on our _currentListType |
| // to remove an existing list. We don't have to do anything if _currentListType is NoList. |
| if (_currentListType == WebKit::OrderedList) |
| _webViewImpl->page().executeEditCommand(@"InsertOrderedList", @""); |
| else if (_currentListType == WebKit::UnorderedList) |
| _webViewImpl->page().executeEditCommand(@"InsertUnorderedList", @""); |
| break; |
| case unorderedListSegment: |
| _webViewImpl->page().executeEditCommand(@"InsertUnorderedList", @""); |
| break; |
| case orderedListSegment: |
| _webViewImpl->page().executeEditCommand(@"InsertOrderedList", @""); |
| break; |
| } |
| |
| _webViewImpl->dismissTextTouchBarPopoverItemWithIdentifier(NSTouchBarItemIdentifierTextList); |
| } |
| |
| - (void)setCurrentListType:(WebKit::ListType)listType |
| { |
| NSSegmentedControl *insertListControl = (NSSegmentedControl *)self.view; |
| switch (listType) { |
| case WebKit::NoList: |
| [insertListControl setSelected:YES forSegment:noListSegment]; |
| break; |
| case WebKit::OrderedList: |
| [insertListControl setSelected:YES forSegment:orderedListSegment]; |
| break; |
| case WebKit::UnorderedList: |
| [insertListControl setSelected:YES forSegment:unorderedListSegment]; |
| break; |
| } |
| |
| _currentListType = listType; |
| } |
| |
| @end |
| |
| @interface WKTextTouchBarItemController : NSTextTouchBarItemController <NSCandidateListTouchBarItemDelegate, NSTouchBarDelegate> { |
| @private |
| BOOL _textIsBold; |
| BOOL _textIsItalic; |
| BOOL _textIsUnderlined; |
| NSTextAlignment _currentTextAlignment; |
| RetainPtr<NSColor> _textColor; |
| RetainPtr<WKTextListTouchBarViewController> _textListTouchBarViewController; |
| |
| @private |
| WebKit::WebViewImpl* _webViewImpl; |
| } |
| |
| @property (nonatomic) BOOL textIsBold; |
| @property (nonatomic) BOOL textIsItalic; |
| @property (nonatomic) BOOL textIsUnderlined; |
| @property (nonatomic) NSTextAlignment currentTextAlignment; |
| @property (nonatomic, retain, readwrite) NSColor *textColor; |
| |
| - (instancetype)initWithWebViewImpl:(WebKit::WebViewImpl*)webViewImpl; |
| @end |
| |
| @implementation WKTextTouchBarItemController |
| |
| @synthesize textIsBold=_textIsBold; |
| @synthesize textIsItalic=_textIsItalic; |
| @synthesize textIsUnderlined=_textIsUnderlined; |
| @synthesize currentTextAlignment=_currentTextAlignment; |
| |
| - (instancetype)initWithWebViewImpl:(WebKit::WebViewImpl*)webViewImpl |
| { |
| if (!(self = [super init])) |
| return nil; |
| |
| _webViewImpl = webViewImpl; |
| |
| return self; |
| } |
| |
| - (void)didDestroyView |
| { |
| [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| _webViewImpl = nullptr; |
| [_textListTouchBarViewController didDestroyView]; |
| } |
| |
| #pragma mark NSTouchBarDelegate |
| |
| - (NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSString *)identifier |
| { |
| return [self itemForIdentifier:identifier]; |
| } |
| |
| - (NSTouchBarItem *)itemForIdentifier:(NSString *)identifier |
| { |
| NSTouchBarItem *item = [super itemForIdentifier:identifier]; |
| BOOL isTextFormatItem = [identifier isEqualToString:NSTouchBarItemIdentifierTextFormat]; |
| |
| if (isTextFormatItem || [identifier isEqualToString:NSTouchBarItemIdentifierTextStyle]) |
| self.textStyle.action = @selector(_wkChangeTextStyle:); |
| |
| if (isTextFormatItem || [identifier isEqualToString:NSTouchBarItemIdentifierTextAlignment]) |
| self.textAlignments.action = @selector(_wkChangeTextAlignment:); |
| |
| NSColorPickerTouchBarItem *colorPickerItem = nil; |
| if ([identifier isEqualToString:NSTouchBarItemIdentifierTextColorPicker] && [item isKindOfClass:[NSColorPickerTouchBarItem class]]) |
| colorPickerItem = (NSColorPickerTouchBarItem *)item; |
| if (isTextFormatItem) |
| colorPickerItem = self.colorPickerItem; |
| if (colorPickerItem) { |
| colorPickerItem.target = self; |
| colorPickerItem.action = @selector(_wkChangeColor:); |
| colorPickerItem.showsAlpha = NO; |
| colorPickerItem.allowedColorSpaces = @[ [NSColorSpace sRGBColorSpace] ]; |
| } |
| |
| return item; |
| } |
| |
| #pragma mark NSCandidateListTouchBarItemDelegate |
| |
| - (void)candidateListTouchBarItem:(NSCandidateListTouchBarItem *)anItem endSelectingCandidateAtIndex:(NSInteger)index |
| { |
| if (index == NSNotFound) |
| return; |
| |
| if (!_webViewImpl) |
| return; |
| |
| NSArray *candidates = anItem.candidates; |
| if ((NSUInteger)index >= candidates.count) |
| return; |
| |
| NSTextCheckingResult *candidate = candidates[index]; |
| ASSERT([candidate isKindOfClass:[NSTextCheckingResult class]]); |
| |
| _webViewImpl->handleAcceptedCandidate(candidate); |
| } |
| |
| - (void)candidateListTouchBarItem:(NSCandidateListTouchBarItem *)anItem changedCandidateListVisibility:(BOOL)isVisible |
| { |
| if (!_webViewImpl) |
| return; |
| |
| if (isVisible) |
| _webViewImpl->requestCandidatesForSelectionIfNeeded(); |
| |
| _webViewImpl->updateTouchBar(); |
| } |
| |
| #pragma mark NSNotificationCenter observers |
| |
| - (void)touchBarDidExitCustomization:(NSNotification *)notification |
| { |
| if (!_webViewImpl) |
| return; |
| |
| _webViewImpl->setIsCustomizingTouchBar(false); |
| _webViewImpl->updateTouchBar(); |
| } |
| |
| - (void)touchBarWillEnterCustomization:(NSNotification *)notification |
| { |
| if (!_webViewImpl) |
| return; |
| |
| _webViewImpl->setIsCustomizingTouchBar(true); |
| } |
| |
| - (void)didChangeAutomaticTextCompletion:(NSNotification *)notification |
| { |
| if (!_webViewImpl) |
| return; |
| |
| _webViewImpl->updateTouchBarAndRefreshTextBarIdentifiers(); |
| } |
| |
| |
| #pragma mark NSTextTouchBarItemController |
| |
| - (WKTextListTouchBarViewController *)textListTouchBarViewController |
| { |
| return (WKTextListTouchBarViewController *)self.textListViewController; |
| } |
| |
| - (void)setTextIsBold:(BOOL)bold |
| { |
| _textIsBold = bold; |
| if ([self.textStyle isSelectedForSegment:0] != _textIsBold) |
| [self.textStyle setSelected:_textIsBold forSegment:0]; |
| } |
| |
| - (void)setTextIsItalic:(BOOL)italic |
| { |
| _textIsItalic = italic; |
| if ([self.textStyle isSelectedForSegment:1] != _textIsItalic) |
| [self.textStyle setSelected:_textIsItalic forSegment:1]; |
| } |
| |
| - (void)setTextIsUnderlined:(BOOL)underlined |
| { |
| _textIsUnderlined = underlined; |
| if ([self.textStyle isSelectedForSegment:2] != _textIsUnderlined) |
| [self.textStyle setSelected:_textIsUnderlined forSegment:2]; |
| } |
| |
| - (void)_wkChangeTextStyle:(id)sender |
| { |
| if (!_webViewImpl) |
| return; |
| |
| if ([self.textStyle isSelectedForSegment:0] != _textIsBold) { |
| _textIsBold = !_textIsBold; |
| _webViewImpl->page().executeEditCommand(@"ToggleBold", @""); |
| } |
| |
| if ([self.textStyle isSelectedForSegment:1] != _textIsItalic) { |
| _textIsItalic = !_textIsItalic; |
| _webViewImpl->page().executeEditCommand("ToggleItalic", @""); |
| } |
| |
| if ([self.textStyle isSelectedForSegment:2] != _textIsUnderlined) { |
| _textIsUnderlined = !_textIsUnderlined; |
| _webViewImpl->page().executeEditCommand("ToggleUnderline", @""); |
| } |
| } |
| |
| - (void)setCurrentTextAlignment:(NSTextAlignment)alignment |
| { |
| _currentTextAlignment = alignment; |
| [self.textAlignments selectSegmentWithTag:_currentTextAlignment]; |
| } |
| |
| - (void)_wkChangeTextAlignment:(id)sender |
| { |
| if (!_webViewImpl) |
| return; |
| |
| NSTextAlignment alignment = (NSTextAlignment)[self.textAlignments.cell tagForSegment:self.textAlignments.selectedSegment]; |
| switch (alignment) { |
| case NSTextAlignmentLeft: |
| _currentTextAlignment = NSTextAlignmentLeft; |
| _webViewImpl->page().executeEditCommand("AlignLeft", @""); |
| break; |
| case NSTextAlignmentRight: |
| _currentTextAlignment = NSTextAlignmentRight; |
| _webViewImpl->page().executeEditCommand("AlignRight", @""); |
| break; |
| case NSTextAlignmentCenter: |
| _currentTextAlignment = NSTextAlignmentCenter; |
| _webViewImpl->page().executeEditCommand("AlignCenter", @""); |
| break; |
| case NSTextAlignmentJustified: |
| _currentTextAlignment = NSTextAlignmentJustified; |
| _webViewImpl->page().executeEditCommand("AlignJustified", @""); |
| break; |
| default: |
| break; |
| } |
| |
| _webViewImpl->dismissTextTouchBarPopoverItemWithIdentifier(NSTouchBarItemIdentifierTextAlignment); |
| } |
| |
| - (NSColor *)textColor |
| { |
| return _textColor.get(); |
| } |
| |
| - (void)setTextColor:(NSColor *)color |
| { |
| _textColor = color; |
| self.colorPickerItem.color = _textColor.get(); |
| } |
| |
| - (void)_wkChangeColor:(id)sender |
| { |
| if (!_webViewImpl) |
| return; |
| |
| _textColor = self.colorPickerItem.color; |
| _webViewImpl->page().executeEditCommand("ForeColor", WebCore::colorFromNSColor(_textColor.get()).serialized()); |
| } |
| |
| - (NSViewController *)textListViewController |
| { |
| if (!_textListTouchBarViewController) |
| _textListTouchBarViewController = adoptNS([[WKTextListTouchBarViewController alloc] initWithWebViewImpl:_webViewImpl]); |
| return _textListTouchBarViewController.get(); |
| } |
| |
| @end |
| |
| @interface WKPromisedAttachmentContext : NSObject { |
| @private |
| RetainPtr<NSURL> _blobURL; |
| RetainPtr<NSString> _fileName; |
| RetainPtr<NSString> _attachmentIdentifier; |
| } |
| |
| - (instancetype)initWithIdentifier:(NSString *)identifier blobURL:(NSURL *)url fileName:(NSString *)fileName; |
| |
| @property (nonatomic, readonly) NSURL *blobURL; |
| @property (nonatomic, readonly) NSString *fileName; |
| @property (nonatomic, readonly) NSString *attachmentIdentifier; |
| |
| @end |
| |
| @implementation WKPromisedAttachmentContext |
| |
| - (instancetype)initWithIdentifier:(NSString *)identifier blobURL:(NSURL *)blobURL fileName:(NSString *)fileName |
| { |
| if (!(self = [super init])) |
| return nil; |
| |
| _blobURL = blobURL; |
| _fileName = fileName; |
| _attachmentIdentifier = identifier; |
| return self; |
| } |
| |
| - (NSURL *)blobURL |
| { |
| return _blobURL.get(); |
| } |
| |
| - (NSString *)fileName |
| { |
| return _fileName.get(); |
| } |
| |
| - (NSString *)attachmentIdentifier |
| { |
| return _attachmentIdentifier.get(); |
| } |
| |
| @end |
| |
| @interface WKDOMPasteMenuDelegate : NSObject<NSMenuDelegate> |
| - (instancetype)initWithWebViewImpl:(WebKit::WebViewImpl&)impl; |
| @end |
| |
| @implementation WKDOMPasteMenuDelegate { |
| WeakPtr<WebKit::WebViewImpl> _impl; |
| } |
| |
| - (instancetype)initWithWebViewImpl:(WebKit::WebViewImpl&)impl |
| { |
| if (!(self = [super init])) |
| return nil; |
| |
| _impl = makeWeakPtr(impl); |
| return self; |
| } |
| |
| - (void)menuDidClose:(NSMenu *)menu |
| { |
| dispatch_async(dispatch_get_main_queue(), [impl = _impl] { |
| if (impl) |
| impl->handleDOMPasteRequestWithResult(WebCore::DOMPasteAccessResponse::DeniedForGesture); |
| }); |
| } |
| |
| - (NSInteger)numberOfItemsInMenu:(NSMenu *)menu |
| { |
| return 1; |
| } |
| |
| - (NSRect)confinementRectForMenu:(NSMenu *)menu onScreen:(NSScreen *)screen |
| { |
| auto confinementRect = WebCore::enclosingIntRect(NSRect { NSEvent.mouseLocation, menu.size }); |
| confinementRect.move(0, -confinementRect.height()); |
| return confinementRect; |
| } |
| |
| @end |
| |
| namespace WebKit { |
| |
| NSTouchBar *WebViewImpl::makeTouchBar() |
| { |
| if (!m_canCreateTouchBars) { |
| m_canCreateTouchBars = true; |
| updateTouchBar(); |
| } |
| return m_currentTouchBar.get(); |
| } |
| |
| void WebViewImpl::updateTouchBar() |
| { |
| if (!m_canCreateTouchBars) |
| return; |
| |
| NSTouchBar *touchBar = nil; |
| bool userActionRequirementsHaveBeenMet = m_requiresUserActionForEditingControlsManager ? m_page->hasHadSelectionChangesFromUserInteraction() : true; |
| if (m_page->editorState().isContentEditable && !m_page->isTouchBarUpdateSupressedForHiddenContentEditable()) { |
| updateTextTouchBar(); |
| if (userActionRequirementsHaveBeenMet) |
| touchBar = textTouchBar(); |
| } |
| #if ENABLE(WEB_PLAYBACK_CONTROLS_MANAGER) |
| else if (m_page->hasActiveVideoForControlsManager()) { |
| updateMediaTouchBar(); |
| // If useMediaPlaybackControlsView() is true, then we are relying on the API client to display a popover version |
| // of the media timeline in their own function bar. If it is false, then we will display the media timeline in our |
| // function bar. |
| if (!useMediaPlaybackControlsView()) |
| touchBar = [m_mediaTouchBarProvider respondsToSelector:@selector(touchBar)] ? [(id)m_mediaTouchBarProvider.get() touchBar] : [(id)m_mediaTouchBarProvider.get() touchBar]; |
| } else if ([m_mediaTouchBarProvider playbackControlsController]) { |
| if (m_clientWantsMediaPlaybackControlsView) { |
| if ([m_view respondsToSelector:@selector(_web_didRemoveMediaControlsManager)] && m_view.getAutoreleased() == [m_view window].firstResponder) |
| [m_view _web_didRemoveMediaControlsManager]; |
| } |
| [m_mediaTouchBarProvider setPlaybackControlsController:nil]; |
| [m_mediaPlaybackControlsView setPlaybackControlsController:nil]; |
| } |
| #endif |
| |
| if (touchBar == m_currentTouchBar) |
| return; |
| |
| // If m_editableElementIsFocused is true, then we may have a non-editable selection right now just because |
| // the user is clicking or tabbing between editable fields. |
| if (m_editableElementIsFocused && touchBar != textTouchBar()) |
| return; |
| |
| m_currentTouchBar = touchBar; |
| [m_view willChangeValueForKey:@"touchBar"]; |
| [m_view setTouchBar:m_currentTouchBar.get()]; |
| [m_view didChangeValueForKey:@"touchBar"]; |
| } |
| |
| NSCandidateListTouchBarItem *WebViewImpl::candidateListTouchBarItem() const |
| { |
| if (m_page->editorState().isInPasswordField) |
| return m_passwordTextCandidateListTouchBarItem.get(); |
| return isRichlyEditableForTouchBar() ? m_richTextCandidateListTouchBarItem.get() : m_plainTextCandidateListTouchBarItem.get(); |
| } |
| |
| #if ENABLE(WEB_PLAYBACK_CONTROLS_MANAGER) |
| AVTouchBarScrubber *WebViewImpl::mediaPlaybackControlsView() const |
| { |
| if (m_page->hasActiveVideoForControlsManager()) |
| return m_mediaPlaybackControlsView.get(); |
| return nil; |
| } |
| #endif |
| |
| bool WebViewImpl::useMediaPlaybackControlsView() const |
| { |
| #if ENABLE(FULLSCREEN_API) |
| if (hasFullScreenWindowController()) |
| return ![m_fullScreenWindowController isFullScreen]; |
| #endif |
| return m_clientWantsMediaPlaybackControlsView; |
| } |
| |
| void WebViewImpl::dismissTextTouchBarPopoverItemWithIdentifier(NSString *identifier) |
| { |
| NSTouchBarItem *foundItem = nil; |
| for (NSTouchBarItem *item in textTouchBar().items) { |
| if ([item.identifier isEqualToString:identifier]) { |
| foundItem = item; |
| break; |
| } |
| |
| if ([item.identifier isEqualToString:NSTouchBarItemIdentifierTextFormat]) { |
| for (NSTouchBarItem *childItem in ((NSGroupTouchBarItem *)item).groupTouchBar.items) { |
| if ([childItem.identifier isEqualToString:identifier]) { |
| foundItem = childItem; |
| break; |
| } |
| } |
| break; |
| } |
| } |
| |
| if ([foundItem isKindOfClass:[NSPopoverTouchBarItem class]]) |
| [(NSPopoverTouchBarItem *)foundItem dismissPopover:nil]; |
| } |
| |
| static NSArray<NSString *> *textTouchBarCustomizationAllowedIdentifiers() |
| { |
| return @[ NSTouchBarItemIdentifierCharacterPicker, NSTouchBarItemIdentifierTextColorPicker, NSTouchBarItemIdentifierTextStyle, NSTouchBarItemIdentifierTextAlignment, NSTouchBarItemIdentifierTextList, NSTouchBarItemIdentifierFlexibleSpace ]; |
| } |
| |
| static NSArray<NSString *> *plainTextTouchBarDefaultItemIdentifiers() |
| { |
| return @[ NSTouchBarItemIdentifierCharacterPicker, NSTouchBarItemIdentifierCandidateList ]; |
| } |
| |
| static NSArray<NSString *> *richTextTouchBarDefaultItemIdentifiers() |
| { |
| return @[ NSTouchBarItemIdentifierCharacterPicker, NSTouchBarItemIdentifierTextFormat, NSTouchBarItemIdentifierCandidateList ]; |
| } |
| |
| static NSArray<NSString *> *passwordTextTouchBarDefaultItemIdentifiers() |
| { |
| return @[ NSTouchBarItemIdentifierCandidateList ]; |
| } |
| |
| void WebViewImpl::updateTouchBarAndRefreshTextBarIdentifiers() |
| { |
| if (m_richTextTouchBar) |
| setUpTextTouchBar(m_richTextTouchBar.get()); |
| |
| if (m_plainTextTouchBar) |
| setUpTextTouchBar(m_plainTextTouchBar.get()); |
| |
| if (m_passwordTextTouchBar) |
| setUpTextTouchBar(m_passwordTextTouchBar.get()); |
| |
| updateTouchBar(); |
| } |
| |
| void WebViewImpl::setUpTextTouchBar(NSTouchBar *touchBar) |
| { |
| NSSet<NSTouchBarItem *> *templateItems = nil; |
| NSArray<NSTouchBarItemIdentifier> *defaultItemIdentifiers = nil; |
| NSArray<NSTouchBarItemIdentifier> *customizationAllowedItemIdentifiers = nil; |
| |
| if (touchBar == m_passwordTextTouchBar.get()) { |
| templateItems = [NSMutableSet setWithObject:m_passwordTextCandidateListTouchBarItem.get()]; |
| defaultItemIdentifiers = passwordTextTouchBarDefaultItemIdentifiers(); |
| } else if (touchBar == m_richTextTouchBar.get()) { |
| templateItems = [NSMutableSet setWithObject:m_richTextCandidateListTouchBarItem.get()]; |
| defaultItemIdentifiers = richTextTouchBarDefaultItemIdentifiers(); |
| customizationAllowedItemIdentifiers = textTouchBarCustomizationAllowedIdentifiers(); |
| } else if (touchBar == m_plainTextTouchBar.get()) { |
| templateItems = [NSMutableSet setWithObject:m_plainTextCandidateListTouchBarItem.get()]; |
| defaultItemIdentifiers = plainTextTouchBarDefaultItemIdentifiers(); |
| customizationAllowedItemIdentifiers = textTouchBarCustomizationAllowedIdentifiers(); |
| } |
| |
| [touchBar setDelegate:m_textTouchBarItemController.get()]; |
| [touchBar setTemplateItems:templateItems]; |
| [touchBar setDefaultItemIdentifiers:defaultItemIdentifiers]; |
| [touchBar setCustomizationAllowedItemIdentifiers:customizationAllowedItemIdentifiers]; |
| |
| if (NSGroupTouchBarItem *textFormatItem = (NSGroupTouchBarItem *)[touchBar itemForIdentifier:NSTouchBarItemIdentifierTextFormat]) |
| textFormatItem.groupTouchBar.customizationIdentifier = @"WKTextFormatTouchBar"; |
| } |
| |
| bool WebViewImpl::isRichlyEditableForTouchBar() const |
| { |
| return m_page->editorState().isContentRichlyEditable && !m_page->isNeverRichlyEditableForTouchBar(); |
| } |
| |
| NSTouchBar *WebViewImpl::textTouchBar() const |
| { |
| if (m_page->editorState().isInPasswordField) |
| return m_passwordTextTouchBar.get(); |
| return isRichlyEditableForTouchBar() ? m_richTextTouchBar.get() : m_plainTextTouchBar.get(); |
| } |
| |
| static NSTextAlignment nsTextAlignmentFromTextAlignment(TextAlignment textAlignment) |
| { |
| NSTextAlignment nsTextAlignment; |
| switch (textAlignment) { |
| case NoAlignment: |
| nsTextAlignment = NSTextAlignmentNatural; |
| break; |
| case LeftAlignment: |
| nsTextAlignment = NSTextAlignmentLeft; |
| break; |
| case RightAlignment: |
| nsTextAlignment = NSTextAlignmentRight; |
| break; |
| case CenterAlignment: |
| nsTextAlignment = NSTextAlignmentCenter; |
| break; |
| case JustifiedAlignment: |
| nsTextAlignment = NSTextAlignmentJustified; |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| return nsTextAlignment; |
| } |
| |
| void WebViewImpl::updateTextTouchBar() |
| { |
| if (!m_page->editorState().isContentEditable) |
| return; |
| |
| if (m_isUpdatingTextTouchBar) |
| return; |
| |
| SetForScope<bool> isUpdatingTextFunctionBar(m_isUpdatingTextTouchBar, true); |
| |
| if (!m_textTouchBarItemController) |
| m_textTouchBarItemController = adoptNS([[WKTextTouchBarItemController alloc] initWithWebViewImpl:this]); |
| |
| if (!m_startedListeningToCustomizationEvents) { |
| [[NSNotificationCenter defaultCenter] addObserver:m_textTouchBarItemController.get() selector:@selector(touchBarDidExitCustomization:) name:NSTouchBarDidExitCustomization object:nil]; |
| [[NSNotificationCenter defaultCenter] addObserver:m_textTouchBarItemController.get() selector:@selector(touchBarWillEnterCustomization:) name:NSTouchBarWillEnterCustomization object:nil]; |
| [[NSNotificationCenter defaultCenter] addObserver:m_textTouchBarItemController.get() selector:@selector(didChangeAutomaticTextCompletion:) name:NSSpellCheckerDidChangeAutomaticTextCompletionNotification object:nil]; |
| |
| m_startedListeningToCustomizationEvents = true; |
| } |
| |
| if (!m_richTextCandidateListTouchBarItem || !m_plainTextCandidateListTouchBarItem || !m_passwordTextCandidateListTouchBarItem) { |
| m_richTextCandidateListTouchBarItem = adoptNS([[NSCandidateListTouchBarItem alloc] initWithIdentifier:NSTouchBarItemIdentifierCandidateList]); |
| [m_richTextCandidateListTouchBarItem setDelegate:m_textTouchBarItemController.get()]; |
| m_plainTextCandidateListTouchBarItem = adoptNS([[NSCandidateListTouchBarItem alloc] initWithIdentifier:NSTouchBarItemIdentifierCandidateList]); |
| [m_plainTextCandidateListTouchBarItem setDelegate:m_textTouchBarItemController.get()]; |
| m_passwordTextCandidateListTouchBarItem = adoptNS([[NSCandidateListTouchBarItem alloc] initWithIdentifier:NSTouchBarItemIdentifierCandidateList]); |
| [m_passwordTextCandidateListTouchBarItem setDelegate:m_textTouchBarItemController.get()]; |
| requestCandidatesForSelectionIfNeeded(); |
| } |
| |
| if (!m_richTextTouchBar) { |
| m_richTextTouchBar = adoptNS([[NSTouchBar alloc] init]); |
| setUpTextTouchBar(m_richTextTouchBar.get()); |
| [m_richTextTouchBar setCustomizationIdentifier:@"WKRichTextTouchBar"]; |
| } |
| |
| if (!m_plainTextTouchBar) { |
| m_plainTextTouchBar = adoptNS([[NSTouchBar alloc] init]); |
| setUpTextTouchBar(m_plainTextTouchBar.get()); |
| [m_plainTextTouchBar setCustomizationIdentifier:@"WKPlainTextTouchBar"]; |
| } |
| |
| if ([NSSpellChecker isAutomaticTextCompletionEnabled] && !m_isCustomizingTouchBar) { |
| BOOL showCandidatesList = !m_page->editorState().selectionIsRange || m_isHandlingAcceptedCandidate; |
| [candidateListTouchBarItem() updateWithInsertionPointVisibility:showCandidatesList]; |
| [m_view _didUpdateCandidateListVisibility:showCandidatesList]; |
| } |
| |
| if (m_page->editorState().isInPasswordField) { |
| if (!m_passwordTextTouchBar) { |
| m_passwordTextTouchBar = adoptNS([[NSTouchBar alloc] init]); |
| setUpTextTouchBar(m_passwordTextTouchBar.get()); |
| } |
| [m_passwordTextCandidateListTouchBarItem setCandidates:@[ ] forSelectedRange:NSMakeRange(0, 0) inString:nil]; |
| } |
| |
| NSTouchBar *textTouchBar = this->textTouchBar(); |
| BOOL isShowingCombinedTextFormatItem = [textTouchBar.defaultItemIdentifiers containsObject:NSTouchBarItemIdentifierTextFormat]; |
| [textTouchBar setPrincipalItemIdentifier:isShowingCombinedTextFormatItem ? NSTouchBarItemIdentifierTextFormat : nil]; |
| |
| // Set current typing attributes for rich text. This will ensure that the buttons reflect the state of |
| // the text when changing selection throughout the document. |
| if (isRichlyEditableForTouchBar()) { |
| const EditorState& editorState = m_page->editorState(); |
| if (!editorState.isMissingPostLayoutData) { |
| [m_textTouchBarItemController setTextIsBold:(bool)(m_page->editorState().postLayoutData().typingAttributes & AttributeBold)]; |
| [m_textTouchBarItemController setTextIsItalic:(bool)(m_page->editorState().postLayoutData().typingAttributes & AttributeItalics)]; |
| [m_textTouchBarItemController setTextIsUnderlined:(bool)(m_page->editorState().postLayoutData().typingAttributes & AttributeUnderline)]; |
| [m_textTouchBarItemController setTextColor:nsColor(editorState.postLayoutData().textColor)]; |
| [[m_textTouchBarItemController textListTouchBarViewController] setCurrentListType:(ListType)m_page->editorState().postLayoutData().enclosingListType]; |
| [m_textTouchBarItemController setCurrentTextAlignment:nsTextAlignmentFromTextAlignment((TextAlignment)editorState.postLayoutData().textAlignment)]; |
| } |
| BOOL isShowingCandidateListItem = [textTouchBar.defaultItemIdentifiers containsObject:NSTouchBarItemIdentifierCandidateList] && [NSSpellChecker isAutomaticTextReplacementEnabled]; |
| [m_textTouchBarItemController setUsesNarrowTextStyleItem:isShowingCombinedTextFormatItem && isShowingCandidateListItem]; |
| } |
| } |
| |
| #if ENABLE(WEB_PLAYBACK_CONTROLS_MANAGER) |
| |
| bool WebViewImpl::isPictureInPictureActive() |
| { |
| return [m_playbackControlsManager isPictureInPictureActive]; |
| } |
| |
| void WebViewImpl::togglePictureInPicture() |
| { |
| [m_playbackControlsManager togglePictureInPicture]; |
| } |
| |
| void WebViewImpl::updateMediaPlaybackControlsManager() |
| { |
| if (!m_page->hasActiveVideoForControlsManager()) |
| return; |
| |
| if (!m_playbackControlsManager) { |
| m_playbackControlsManager = adoptNS([[WebPlaybackControlsManager alloc] init]); |
| [m_playbackControlsManager setAllowsPictureInPicturePlayback:m_page->preferences().allowsPictureInPictureMediaPlayback()]; |
| [m_playbackControlsManager setCanTogglePictureInPicture:NO]; |
| } |
| |
| if (PlatformPlaybackSessionInterface* interface = m_page->playbackSessionManager()->controlsManagerInterface()) { |
| [m_playbackControlsManager setPlaybackSessionInterfaceMac:interface]; |
| interface->updatePlaybackControlsManagerCanTogglePictureInPicture(); |
| } |
| } |
| |
| #endif // ENABLE(WEB_PLAYBACK_CONTROLS_MANAGER) |
| |
| void WebViewImpl::updateMediaTouchBar() |
| { |
| #if ENABLE(WEB_PLAYBACK_CONTROLS_MANAGER) && ENABLE(VIDEO_PRESENTATION_MODE) |
| if (!m_mediaTouchBarProvider) { |
| m_mediaTouchBarProvider = adoptNS([allocAVTouchBarPlaybackControlsProviderInstance() init]); |
| } |
| |
| if (!m_mediaPlaybackControlsView) { |
| m_mediaPlaybackControlsView = adoptNS([allocAVTouchBarScrubberInstance() init]); |
| // FIXME: Remove this once setCanShowMediaSelectionButton: is declared in an SDK used by Apple's buildbot. |
| if ([m_mediaPlaybackControlsView respondsToSelector:@selector(setCanShowMediaSelectionButton:)]) |
| [m_mediaPlaybackControlsView setCanShowMediaSelectionButton:YES]; |
| } |
| |
| updateMediaPlaybackControlsManager(); |
| |
| [m_mediaTouchBarProvider setPlaybackControlsController:m_playbackControlsManager.get()]; |
| [m_mediaPlaybackControlsView setPlaybackControlsController:m_playbackControlsManager.get()]; |
| |
| if (!useMediaPlaybackControlsView()) { |
| #if ENABLE(FULLSCREEN_API) |
| // If we can't have a media popover function bar item, it might be because we are in full screen. |
| // If so, customize the escape key. |
| NSTouchBar *touchBar = [m_mediaTouchBarProvider respondsToSelector:@selector(touchBar)] ? [(id)m_mediaTouchBarProvider.get() touchBar] : [(id)m_mediaTouchBarProvider.get() touchBar]; |
| if (hasFullScreenWindowController() && [m_fullScreenWindowController isFullScreen]) { |
| if (!m_exitFullScreenButton) { |
| m_exitFullScreenButton = adoptNS([[NSCustomTouchBarItem alloc] initWithIdentifier:WKMediaExitFullScreenItem]); |
| |
| NSImage *image = [NSImage imageNamed:NSImageNameTouchBarExitFullScreenTemplate]; |
| [image setTemplate:YES]; |
| |
| NSButton *exitFullScreenButton = [NSButton buttonWithTitle:image ? @"" : @"Exit" image:image target:m_fullScreenWindowController.get() action:@selector(requestExitFullScreen)]; |
| [exitFullScreenButton setAccessibilityTitle:WebCore::exitFullScreenButtonAccessibilityTitle()]; |
| |
| [[exitFullScreenButton.widthAnchor constraintLessThanOrEqualToConstant:exitFullScreenButtonWidth] setActive:YES]; |
| [m_exitFullScreenButton setView:exitFullScreenButton]; |
| } |
| touchBar.escapeKeyReplacementItem = m_exitFullScreenButton.get(); |
| } else |
| touchBar.escapeKeyReplacementItem = nil; |
| #endif |
| // The rest of the work to update the media function bar only applies to the popover version, so return early. |
| return; |
| } |
| |
| if (m_playbackControlsManager && m_view.getAutoreleased() == [m_view window].firstResponder && [m_view respondsToSelector:@selector(_web_didAddMediaControlsManager:)]) |
| [m_view _web_didAddMediaControlsManager:m_mediaPlaybackControlsView.get()]; |
| #endif |
| } |
| |
| #if HAVE(TOUCH_BAR) |
| |
| bool WebViewImpl::canTogglePictureInPicture() |
| { |
| #if ENABLE(WEB_PLAYBACK_CONTROLS_MANAGER) |
| return [m_playbackControlsManager canTogglePictureInPicture]; |
| #else |
| return NO; |
| #endif |
| } |
| |
| #endif // HAVE(TOUCH_BAR) |
| |
| void WebViewImpl::forceRequestCandidatesForTesting() |
| { |
| m_canCreateTouchBars = true; |
| #if HAVE(TOUCH_BAR) |
| updateTouchBar(); |
| #endif |
| } |
| |
| bool WebViewImpl::shouldRequestCandidates() const |
| { |
| return !m_page->editorState().isInPasswordField && candidateListTouchBarItem().candidateListVisible; |
| } |
| |
| void WebViewImpl::setEditableElementIsFocused(bool editableElementIsFocused) |
| { |
| m_editableElementIsFocused = editableElementIsFocused; |
| |
| #if HAVE(TOUCH_BAR) |
| // If the editable elements have blurred, then we might need to get rid of the editing function bar. |
| if (!m_editableElementIsFocused) |
| updateTouchBar(); |
| #endif |
| } |
| |
| } // namespace WebKit |
| #else // !HAVE(TOUCH_BAR) |
| namespace WebKit { |
| |
| void WebViewImpl::forceRequestCandidatesForTesting() |
| { |
| } |
| |
| bool WebViewImpl::shouldRequestCandidates() const |
| { |
| return false; |
| } |
| |
| void WebViewImpl::setEditableElementIsFocused(bool editableElementIsFocused) |
| { |
| m_editableElementIsFocused = editableElementIsFocused; |
| } |
| |
| } // namespace WebKit |
| #endif // HAVE(TOUCH_BAR) |
| |
| 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 (_NSRecommendedScrollerStyle() == NSScrollerStyleLegacy) |
| options |= NSTrackingActiveAlways; |
| else |
| options |= NSTrackingActiveInKeyWindow; |
| return options; |
| } |
| |
| WebViewImpl::WebViewImpl(NSView <WebViewImplDelegate> *view, WKWebView *outerWebView, WebProcessPool& processPool, Ref<API::PageConfiguration>&& configuration) |
| : m_view(view) |
| , m_pageClient(makeUnique<PageClientImpl>(view, outerWebView)) |
| , m_page(processPool.createWebPage(*m_pageClient, WTFMove(configuration))) |
| , m_needsViewFrameInWindowCoordinates(m_page->preferences().pluginsEnabled()) |
| , m_intrinsicContentSize(CGSizeMake(NSViewNoIntrinsicMetric, NSViewNoIntrinsicMetric)) |
| , m_layoutStrategy([WKViewLayoutStrategy layoutStrategyWithPage:m_page view:view viewImpl:*this mode:kWKLayoutModeViewSize]) |
| , m_undoTarget(adoptNS([[WKEditorUndoTarget alloc] init])) |
| , m_windowVisibilityObserver(adoptNS([[WKWindowVisibilityObserver alloc] initWithView:view impl:*this])) |
| , m_accessibilitySettingsObserver(adoptNS([[WKAccessibilitySettingsObserver alloc] initWithImpl:*this])) |
| , m_primaryTrackingArea(adoptNS([[NSTrackingArea alloc] initWithRect:view.frame options:trackingAreaOptions() owner:view userInfo:nil])) |
| { |
| static_cast<PageClientImpl&>(*m_pageClient).setImpl(*this); |
| |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer)); |
| [NSApp registerServicesMenuSendTypes:PasteboardTypes::forSelection() returnTypes:PasteboardTypes::forEditing()]; |
| |
| [view addTrackingArea:m_primaryTrackingArea.get()]; |
| |
| // Create an NSView that will host our layer tree. |
| m_layerHostingView = adoptNS([[WKFlippedView alloc] initWithFrame:[m_view bounds]]); |
| [m_layerHostingView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; |
| |
| [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_page->setIntrinsicDeviceScaleFactor(intrinsicDeviceScaleFactor()); |
| |
| if (Class gestureClass = NSClassFromString(@"NSImmediateActionGestureRecognizer")) { |
| m_immediateActionGestureRecognizer = adoptNS([(NSImmediateActionGestureRecognizer *)[gestureClass alloc] init]); |
| m_immediateActionController = adoptNS([[WKImmediateActionController alloc] initWithPage:m_page view:view viewImpl:*this recognizer:m_immediateActionGestureRecognizer.get()]); |
| [m_immediateActionGestureRecognizer setDelegate:m_immediateActionController.get()]; |
| [m_immediateActionGestureRecognizer setDelaysPrimaryMouseButtonEvents:NO]; |
| } |
| |
| m_page->setAddsVisitedLinks(processPool.historyClient().addsVisitedLinks()); |
| |
| m_page->initializeWebPage(); |
| |
| registerDraggedTypes(); |
| |
| view.wantsLayer = YES; |
| |
| // Explicitly set the layer contents placement so AppKit will make sure that our layer has masksToBounds set to YES. |
| view.layerContentsPlacement = NSViewLayerContentsPlacementTopLeft; |
| |
| #if ENABLE(FULLSCREEN_API) |
| m_page->setFullscreenClient(makeUnique<WebKit::FullscreenClient>(view)); |
| #endif |
| |
| WebProcessPool::statistics().wkViewCount++; |
| } |
| |
| WebViewImpl::~WebViewImpl() |
| { |
| if (m_remoteObjectRegistry) { |
| m_page->process().processPool().removeMessageReceiver(Messages::RemoteObjectRegistry::messageReceiverName(), m_page->identifier()); |
| [m_remoteObjectRegistry _invalidate]; |
| m_remoteObjectRegistry = nil; |
| } |
| |
| ASSERT(!m_inSecureInputState); |
| ASSERT(!m_thumbnailView); |
| |
| [m_layoutStrategy invalidate]; |
| |
| [m_immediateActionController willDestroyView:m_view.getAutoreleased()]; |
| |
| #if HAVE(TOUCH_BAR) |
| [m_textTouchBarItemController didDestroyView]; |
| #if ENABLE(WEB_PLAYBACK_CONTROLS_MANAGER) |
| [m_mediaTouchBarProvider setPlaybackControlsController:nil]; |
| [m_mediaPlaybackControlsView setPlaybackControlsController:nil]; |
| #endif |
| #endif |
| |
| m_page->close(); |
| |
| WebProcessPool::statistics().wkViewCount--; |
| |
| } |
| |
| NSWindow *WebViewImpl::window() |
| { |
| return [m_view window]; |
| } |
| |
| void WebViewImpl::handleProcessSwapOrExit() |
| { |
| dismissContentRelativeChildWindowsWithAnimation(true); |
| |
| notifyInputContextAboutDiscardedComposition(); |
| |
| updateRemoteAccessibilityRegistration(false); |
| flushPendingMouseEventCallbacks(); |
| |
| handleDOMPasteRequestWithResult(WebCore::DOMPasteAccessResponse::DeniedForGesture); |
| } |
| |
| void WebViewImpl::processWillSwap() |
| { |
| handleProcessSwapOrExit(); |
| if (m_gestureController) |
| m_gestureController->disconnectFromProcess(); |
| } |
| |
| void WebViewImpl::processDidExit() |
| { |
| handleProcessSwapOrExit(); |
| m_gestureController = nullptr; |
| } |
| |
| void WebViewImpl::pageClosed() |
| { |
| updateRemoteAccessibilityRegistration(false); |
| } |
| |
| void WebViewImpl::didRelaunchProcess() |
| { |
| if (m_gestureController) |
| m_gestureController->connectToProcess(); |
| |
| accessibilityRegisterUIProcessTokens(); |
| windowDidChangeScreen(); // Make sure DisplayID is set. |
| } |
| |
| void WebViewImpl::setDrawsBackground(bool drawsBackground) |
| { |
| Optional<WebCore::Color> backgroundColor; |
| if (!drawsBackground) |
| backgroundColor = WebCore::Color(WebCore::Color::transparent); |
| m_page->setBackgroundColor(backgroundColor); |
| |
| // Make sure updateLayer gets called on the web view. |
| [m_view setNeedsDisplay:YES]; |
| } |
| |
| bool WebViewImpl::drawsBackground() const |
| { |
| auto& backgroundColor = m_page->backgroundColor(); |
| return !backgroundColor || backgroundColor.value().isVisible(); |
| } |
| |
| void WebViewImpl::setBackgroundColor(NSColor *backgroundColor) |
| { |
| m_backgroundColor = backgroundColor; |
| |
| // Make sure updateLayer gets called on the web view. |
| [m_view setNeedsDisplay:YES]; |
| } |
| |
| NSColor *WebViewImpl::backgroundColor() const |
| { |
| if (!m_backgroundColor) |
| #if ENABLE(DARK_MODE_CSS) |
| return [NSColor controlBackgroundColor]; |
| #else |
| return [NSColor whiteColor]; |
| #endif |
| return m_backgroundColor.get(); |
| } |
| |
| bool WebViewImpl::isOpaque() const |
| { |
| return drawsBackground(); |
| } |
| |
| void WebViewImpl::setShouldSuppressFirstResponderChanges(bool shouldSuppress) |
| { |
| m_pageClient->setShouldSuppressFirstResponderChanges(shouldSuppress); |
| } |
| |
| bool WebViewImpl::acceptsFirstResponder() |
| { |
| return true; |
| } |
| |
| bool WebViewImpl::becomeFirstResponder() |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer)); |
| // 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->activityStateDidChange(WebCore::ActivityState::IsFocused); |
| // Restore the selection in the editable region if resigning first responder cleared selection. |
| m_page->restoreSelectionInFocusedEditableElement(); |
| |
| m_inBecomeFirstResponder = false; |
| |
| #if HAVE(TOUCH_BAR) |
| updateTouchBar(); |
| #endif |
| |
| if (direction != NSDirectSelection) { |
| NSEvent *event = [NSApp currentEvent]; |
| NSEvent *keyboardEvent = nil; |
| if ([event type] == NSEventTypeKeyDown || [event type] == NSEventTypeKeyUp) |
| keyboardEvent = event; |
| m_page->setInitialFocus(direction == NSSelectingNext, keyboardEvent != nil, NativeWebKeyboardEvent(keyboardEvent, false, false, { }), [](WebKit::CallbackBase::Error) { }); |
| } |
| return true; |
| } |
| |
| bool WebViewImpl::resignFirstResponder() |
| { |
| // 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; |
| } |
| |
| m_willBecomeFirstResponderAgain = false; |
| m_inResignFirstResponder = true; |
| |
| m_page->confirmCompositionAsync(); |
| |
| notifyInputContextAboutDiscardedComposition(); |
| |
| resetSecureInputState(); |
| |
| if (!m_page->maintainsInactiveSelection()) |
| m_page->clearSelection(); |
| |
| m_page->activityStateDidChange(WebCore::ActivityState::IsFocused); |
| |
| m_inResignFirstResponder = false; |
| |
| return true; |
| } |
| |
| void WebViewImpl::takeFocus(WebCore::FocusDirection direction) |
| { |
| NSView *webView = m_view.getAutoreleased(); |
| |
| if (direction == WebCore::FocusDirectionForward) { |
| // Since we're trying to move focus out of m_webView, and because |
| // m_webView may contain subviews within it, we ask it for the next key |
| // view of the last view in its key view loop. This makes m_webView |
| // behave as if it had no subviews, which is the behavior we want. |
| [webView.window selectKeyViewFollowingView:[webView _findLastViewInKeyViewLoop]]; |
| } else |
| [webView.window selectKeyViewPrecedingView:webView]; |
| } |
| |
| void WebViewImpl::showSafeBrowsingWarning(const SafeBrowsingWarning& warning, CompletionHandler<void(Variant<ContinueUnsafeLoad, URL>&&)>&& completionHandler) |
| { |
| if (!m_view) |
| return completionHandler(ContinueUnsafeLoad::Yes); |
| |
| m_safeBrowsingWarning = adoptNS([[WKSafeBrowsingWarning alloc] initWithFrame:[m_view bounds] safeBrowsingWarning:warning completionHandler:[weakThis = makeWeakPtr(*this), completionHandler = WTFMove(completionHandler)] (auto&& result) mutable { |
| completionHandler(WTFMove(result)); |
| if (!weakThis) |
| return; |
| bool navigatesFrame = WTF::switchOn(result, |
| [] (ContinueUnsafeLoad continueUnsafeLoad) { return continueUnsafeLoad == ContinueUnsafeLoad::Yes; }, |
| [] (const URL&) { return true; } |
| ); |
| bool forMainFrameNavigation = [weakThis->m_safeBrowsingWarning forMainFrameNavigation]; |
| if (navigatesFrame && forMainFrameNavigation) { |
| // The safe browsing warning will be hidden once the next page is shown. |
| return; |
| } |
| if (!navigatesFrame && weakThis->m_safeBrowsingWarning && !forMainFrameNavigation) { |
| weakThis->m_page->goBack(); |
| return; |
| } |
| [std::exchange(weakThis->m_safeBrowsingWarning, nullptr) removeFromSuperview]; |
| }]); |
| [m_view addSubview:m_safeBrowsingWarning.get()]; |
| } |
| |
| void WebViewImpl::clearSafeBrowsingWarning() |
| { |
| [std::exchange(m_safeBrowsingWarning, nullptr) removeFromSuperview]; |
| } |
| |
| void WebViewImpl::clearSafeBrowsingWarningIfForMainFrameNavigation() |
| { |
| if ([m_safeBrowsingWarning forMainFrameNavigation]) |
| clearSafeBrowsingWarning(); |
| } |
| |
| bool WebViewImpl::isFocused() const |
| { |
| if (m_inBecomeFirstResponder) |
| return true; |
| if (m_inResignFirstResponder) |
| return false; |
| return [m_view window].firstResponder == m_view.getAutoreleased(); |
| } |
| |
| 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]; |
| [m_safeBrowsingWarning setFrame:[m_view bounds]]; |
| } |
| |
| 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 scrollDelta) |
| { |
| if (!CGSizeEqualToSize(scrollDelta, CGSizeZero)) |
| m_scrollOffsetAdjustment = scrollDelta; |
| |
| [m_view frame] = NSRectFromCGRect(frame); |
| } |
| |
| void WebViewImpl::updateWindowAndViewFrames() |
| { |
| if (clipsToVisibleRect()) |
| updateViewExposedRect(); |
| |
| if (m_didScheduleWindowAndViewFrameUpdate) |
| return; |
| |
| m_didScheduleWindowAndViewFrameUpdate = true; |
| |
| auto weakThis = makeWeakPtr(*this); |
| 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]; |
| |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| if (WebCore::AXObjectCache::accessibilityEnabled()) |
| accessibilityPosition = [[weakThis->m_view accessibilityAttributeValue:NSAccessibilityPositionAttribute] pointValue]; |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| |
| weakThis->m_page->windowAndViewFramesChanged(viewFrameInWindowCoordinates, accessibilityPosition); |
| }); |
| } |
| |
| void WebViewImpl::setFixedLayoutSize(CGSize fixedLayoutSize) |
| { |
| m_lastRequestedFixedLayoutSize = fixedLayoutSize; |
| |
| if (supportsArbitraryLayoutModes()) |
| m_page->setFixedLayoutSize(WebCore::expandedIntSize(WebCore::FloatSize(fixedLayoutSize))); |
| } |
| |
| CGSize WebViewImpl::fixedLayoutSize() const |
| { |
| return m_page->fixedLayoutSize(); |
| } |
| |
| std::unique_ptr<WebKit::DrawingAreaProxy> WebViewImpl::createDrawingAreaProxy(WebProcessProxy& process) |
| { |
| if ([[[NSUserDefaults standardUserDefaults] objectForKey:@"WebKit2UseRemoteLayerTreeDrawingArea"] boolValue]) |
| return makeUnique<RemoteLayerTreeDrawingAreaProxy>(m_page, process); |
| |
| return makeUnique<TiledCoreAnimationDrawingAreaProxy>(m_page, process); |
| } |
| |
| bool WebViewImpl::isUsingUISideCompositing() const |
| { |
| auto* drawingArea = m_page->drawingArea(); |
| return drawingArea && drawingArea->type() == DrawingAreaTypeRemoteLayerTree; |
| } |
| |
| void WebViewImpl::setDrawingAreaSize(CGSize size) |
| { |
| if (!m_page->drawingArea()) |
| return; |
| |
| m_page->drawingArea()->setSize(WebCore::IntSize(size), WebCore::IntSize(m_scrollOffsetAdjustment)); |
| m_scrollOffsetAdjustment = CGSizeZero; |
| } |
| |
| void WebViewImpl::updateLayer() |
| { |
| [m_view layer].backgroundColor = drawsBackground() ? [backgroundColor() CGColor] : CGColorGetConstantColor(kCGColorClear); |
| } |
| |
| void WebViewImpl::drawRect(CGRect rect) |
| { |
| LOG(Printing, "drawRect: x:%g, y:%g, width:%g, height:%g", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); |
| m_page->endPrinting(); |
| } |
| |
| bool WebViewImpl::canChangeFrameLayout(WebFrameProxy& frame) |
| { |
| // PDF documents are already paginated, so we can't change them to add headers and footers. |
| return !frame.isDisplayingPDFDocument(); |
| } |
| |
| NSPrintOperation *WebViewImpl::printOperationWithPrintInfo(NSPrintInfo *printInfo, WebFrameProxy& frame) |
| { |
| LOG(Printing, "Creating an NSPrintOperation for frame '%s'", frame.url().string().utf8().data()); |
| |
| // FIXME: If the frame cannot be printed (e.g. if it contains an encrypted PDF that disallows |
| // printing), this function should return nil. |
| RetainPtr<WKPrintingView> printingView = adoptNS([[WKPrintingView alloc] initWithFrameProxy:frame view:m_view.getAutoreleased()]); |
| // NSPrintOperation takes ownership of the view. |
| NSPrintOperation *printOperation = [NSPrintOperation printOperationWithView:printingView.get() printInfo:printInfo]; |
| [printOperation setCanSpawnSeparateThread:YES]; |
| [printOperation setJobTitle:frame.title()]; |
| printingView->_printOperation = printOperation; |
| return printOperation; |
| } |
| |
| void WebViewImpl::setAutomaticallyAdjustsContentInsets(bool automaticallyAdjustsContentInsets) |
| { |
| m_automaticallyAdjustsContentInsets = automaticallyAdjustsContentInsets; |
| updateContentInsetsIfAutomatic(); |
| } |
| |
| void WebViewImpl::updateContentInsetsIfAutomatic() |
| { |
| if (!m_automaticallyAdjustsContentInsets) |
| return; |
| |
| NSWindow *window = [m_view window]; |
| if ((window.styleMask & NSWindowStyleMaskFullSizeContentView) && !window.titlebarAppearsTransparent && ![m_view enclosingScrollView]) { |
| NSRect contentLayoutRect = [m_view convertRect:window.contentLayoutRect fromView:nil]; |
| CGFloat newTopContentInset = NSMaxY(contentLayoutRect) - NSHeight(contentLayoutRect); |
| if (m_page->topContentInset() != newTopContentInset) |
| setTopContentInset(newTopContentInset); |
| } else |
| setTopContentInset(0); |
| } |
| |
| CGFloat WebViewImpl::topContentInset() const |
| { |
| if (m_didScheduleSetTopContentInset) |
| return m_pendingTopContentInset; |
| return m_page->topContentInset(); |
| } |
| |
| void WebViewImpl::setTopContentInset(CGFloat contentInset) |
| { |
| m_pendingTopContentInset = contentInset; |
| |
| if (m_didScheduleSetTopContentInset) |
| return; |
| |
| m_didScheduleSetTopContentInset = true; |
| |
| auto weakThis = makeWeakPtr(*this); |
| 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_pendingTopContentInset); |
| } |
| |
| void WebViewImpl::prepareContentInRect(CGRect rect) |
| { |
| m_contentPreparationRect = rect; |
| m_useContentPreparationRectForVisibleRect = true; |
| |
| updateViewExposedRect(); |
| } |
| |
| void WebViewImpl::updateViewExposedRect() |
| { |
| CGRect exposedRect = NSRectToCGRect([m_view visibleRect]); |
| |
| if (m_useContentPreparationRectForVisibleRect) |
| exposedRect = CGRectUnion(m_contentPreparationRect, exposedRect); |
| |
| if (auto drawingArea = m_page->drawingArea()) |
| drawingArea->setViewExposedRect(m_clipsToVisibleRect ? Optional<WebCore::FloatRect>(exposedRect) : WTF::nullopt); |
| } |
| |
| void WebViewImpl::setClipsToVisibleRect(bool clipsToVisibleRect) |
| { |
| m_clipsToVisibleRect = clipsToVisibleRect; |
| updateViewExposedRect(); |
| } |
| |
| void WebViewImpl::setMinimumSizeForAutoLayout(CGSize minimumSizeForAutoLayout) |
| { |
| bool expandsToFit = minimumSizeForAutoLayout.width > 0; |
| |
| m_page->setMinimumSizeForAutoLayout(WebCore::IntSize(minimumSizeForAutoLayout)); |
| m_page->setMainFrameIsScrollable(!expandsToFit); |
| |
| setClipsToVisibleRect(expandsToFit); |
| } |
| |
| CGSize WebViewImpl::minimumSizeForAutoLayout() const |
| { |
| return m_page->minimumSizeForAutoLayout(); |
| } |
| |
| void WebViewImpl::setShouldExpandToViewHeightForAutoLayout(bool shouldExpandToViewHeightForAutoLayout) |
| { |
| m_page->setAutoSizingShouldExpandToViewHeight(shouldExpandToViewHeightForAutoLayout); |
| } |
| |
| bool WebViewImpl::shouldExpandToViewHeightForAutoLayout() const |
| { |
| return m_page->autoSizingShouldExpandToViewHeight(); |
| } |
| |
| 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->minimumSizeForAutoLayout().width()) |
| intrinsicContentSizeAcknowledgingFlexibleWidth.width = NSViewNoIntrinsicMetric; |
| |
| 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; |
| |
| 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.getAutoreleased() 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; |
| CGSize oldRequestedFixedLayoutSize = m_lastRequestedFixedLayoutSize; |
| setViewScale(1); |
| setLayoutMode(kWKLayoutModeViewSize); |
| setFixedLayoutSize(CGSizeZero); |
| |
| // 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; |
| m_lastRequestedFixedLayoutSize = oldRequestedFixedLayoutSize; |
| } else if (m_lastRequestedLayoutMode != [m_layoutStrategy layoutMode]) { |
| setViewScale(m_lastRequestedViewScale); |
| setLayoutMode(m_lastRequestedLayoutMode); |
| setFixedLayoutSize(m_lastRequestedFixedLayoutSize); |
| } |
| } |
| |
| 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() |
| { |
| LOG(ActivityState, "WebViewImpl %p (page %llu) windowDidOrderOffScreen", this, m_page->identifier().toUInt64()); |
| m_page->activityStateDidChange({ WebCore::ActivityState::IsVisible, WebCore::ActivityState::WindowIsActive }); |
| } |
| |
| void WebViewImpl::windowDidOrderOnScreen() |
| { |
| LOG(ActivityState, "WebViewImpl %p (page %llu) windowDidOrderOnScreen", this, m_page->identifier().toUInt64()); |
| m_page->activityStateDidChange({ WebCore::ActivityState::IsVisible, WebCore::ActivityState::WindowIsActive }); |
| } |
| |
| void WebViewImpl::windowDidBecomeKey(NSWindow *keyWindow) |
| { |
| if (keyWindow == [m_view window] || keyWindow == [m_view window].attachedSheet) { |
| #if ENABLE(GAMEPAD) |
| UIGamepadProvider::singleton().viewBecameActive(m_page.get()); |
| #endif |
| updateSecureInputState(); |
| m_page->activityStateDidChange(WebCore::ActivityState::WindowIsActive); |
| } |
| } |
| |
| void WebViewImpl::windowDidResignKey(NSWindow *formerKeyWindow) |
| { |
| if (formerKeyWindow == [m_view window] || formerKeyWindow == [m_view window].attachedSheet) { |
| #if ENABLE(GAMEPAD) |
| UIGamepadProvider::singleton().viewBecameInactive(m_page.get()); |
| #endif |
| updateSecureInputState(); |
| m_page->activityStateDidChange(WebCore::ActivityState::WindowIsActive); |
| } |
| } |
| |
| void WebViewImpl::windowDidMiniaturize() |
| { |
| m_page->activityStateDidChange(WebCore::ActivityState::IsVisible); |
| } |
| |
| void WebViewImpl::windowDidDeminiaturize() |
| { |
| m_page->activityStateDidChange(WebCore::ActivityState::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([[[[window screen] deviceDescription] objectForKey:@"NSScreenNumber"] intValue]); |
| } |
| |
| void WebViewImpl::windowDidChangeLayerHosting() |
| { |
| m_page->layerHostingModeDidChange(); |
| } |
| |
| void WebViewImpl::windowDidChangeOcclusionState() |
| { |
| LOG(ActivityState, "WebViewImpl %p (page %llu) windowDidChangeOcclusionState", this, m_page->identifier().toUInt64()); |
| m_page->activityStateDidChange(WebCore::ActivityState::IsVisible); |
| } |
| |
| bool WebViewImpl::mightBeginDragWhileInactive() |
| { |
| if ([m_view window].isKeyWindow) |
| return false; |
| |
| if (m_page->editorState().selectionIsNone || !m_page->editorState().selectionIsRange) |
| return false; |
| |
| return true; |
| } |
| |
| bool WebViewImpl::mightBeginScrollWhileInactive() |
| { |
| // Legacy style scrollbars have design details that rely on tracking the mouse all the time. |
| if (_NSRecommendedScrollerStyle() == NSScrollerStyleLegacy) |
| return true; |
| |
| return false; |
| } |
| |
| void WebViewImpl::accessibilitySettingsDidChange() |
| { |
| m_page->accessibilitySettingsDidChange(); |
| } |
| |
| bool WebViewImpl::acceptsFirstMouse(NSEvent *event) |
| { |
| if (!mightBeginDragWhileInactive() && !mightBeginScrollWhileInactive()) |
| return false; |
| |
| // There's a chance that responding to this event will run a nested event loop, and |
| // fetching a new event might release the old one. Retaining and then autoreleasing |
| // the current event prevents that from causing a problem inside WebKit or AppKit code. |
| CFRetain((__bridge CFTypeRef)event); |
| CFAutorelease((__bridge CFTypeRef)event); |
| |
| if (![m_view hitTest:event.locationInWindow]) |
| return false; |
| |
| setLastMouseDownEvent(event); |
| bool result = m_page->acceptsFirstMouse(event.eventNumber, WebEventFactory::createWebMouseEvent(event, m_lastPressureEvent.get(), m_view.getAutoreleased())); |
| setLastMouseDownEvent(nil); |
| return result; |
| } |
| |
| bool WebViewImpl::shouldDelayWindowOrderingForEvent(NSEvent *event) |
| { |
| if (!mightBeginDragWhileInactive()) |
| return false; |
| |
| // There's a chance that responding to this event will run a nested event loop, and |
| // fetching a new event might release the old one. Retaining and then autoreleasing |
| // the current event prevents that from causing a problem inside WebKit or AppKit code. |
| CFRetain((__bridge CFTypeRef)event); |
| CFAutorelease((__bridge CFTypeRef)event); |
| |
| if (![m_view hitTest:event.locationInWindow]) |
| return false; |
| |
| setLastMouseDownEvent(event); |
| bool result = m_page->shouldDelayWindowOrderingForEvent(WebEventFactory::createWebMouseEvent(event, m_lastPressureEvent.get(), m_view.getAutoreleased())); |
| setLastMouseDownEvent(nil); |
| return result; |
| } |
| |
| bool WebViewImpl::windowResizeMouseLocationIsInVisibleScrollerThumb(CGPoint point) |
| { |
| NSPoint localPoint = [m_view convertPoint:NSPointFromCGPoint(point) fromView:nil]; |
| NSRect visibleThumbRect = NSRect(m_page->visibleScrollerThumbRect()); |
| return NSMouseInRect(localPoint, visibleThumbRect, [m_view isFlipped]); |
| } |
| |
| 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]; |
| |
| LOG(ActivityState, "WebViewImpl %p viewDidMoveToWindow %p", this, window); |
| |
| if (window) { |
| windowDidChangeScreen(); |
| |
| OptionSet<WebCore::ActivityState::Flag> activityStateChanges { WebCore::ActivityState::WindowIsActive, WebCore::ActivityState::IsVisible }; |
| if (m_shouldDeferViewInWindowChanges) |
| m_viewInWindowChangeWasDeferred = true; |
| else |
| activityStateChanges.add(WebCore::ActivityState::IsInWindow); |
| m_page->activityStateDidChange(activityStateChanges); |
| |
| updateWindowAndViewFrames(); |
| |
| // FIXME(135509) This call becomes unnecessary once 135509 is fixed; remove. |
| m_page->layerHostingModeDidChange(); |
| |
| if (!m_flagsChangedEventMonitor) { |
| auto weakThis = makeWeakPtr(*this); |
| m_flagsChangedEventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskFlagsChanged handler:[weakThis] (NSEvent *flagsChangedEvent) { |
| if (weakThis) |
| weakThis->postFakeMouseMovedEventForFlagsChangedEvent(flagsChangedEvent); |
| return flagsChangedEvent; |
| }]; |
| } |
| |
| accessibilityRegisterUIProcessTokens(); |
| |
| if (m_immediateActionGestureRecognizer && ![[m_view gestureRecognizers] containsObject:m_immediateActionGestureRecognizer.get()] && !m_ignoresNonWheelEvents && m_allowsLinkPreview) |
| [m_view addGestureRecognizer:m_immediateActionGestureRecognizer.get()]; |
| } else { |
| OptionSet<WebCore::ActivityState::Flag> activityStateChanges { WebCore::ActivityState::WindowIsActive, WebCore::ActivityState::IsVisible }; |
| if (m_shouldDeferViewInWindowChanges) |
| m_viewInWindowChangeWasDeferred = true; |
| else |
| activityStateChanges.add(WebCore::ActivityState::IsInWindow); |
| m_page->activityStateDidChange(activityStateChanges); |
| |
| [NSEvent removeMonitor:m_flagsChangedEventMonitor]; |
| m_flagsChangedEventMonitor = nil; |
| |
| dismissContentRelativeChildWindowsWithAnimation(false); |
| |
| if (m_immediateActionGestureRecognizer) { |
| // Work around <rdar://problem/22646404> by explicitly cancelling the animation. |
| cancelImmediateActionAnimation(); |
| [m_view removeGestureRecognizer:m_immediateActionGestureRecognizer.get()]; |
| } |
| } |
| |
| m_page->setIntrinsicDeviceScaleFactor(intrinsicDeviceScaleFactor()); |
| m_page->webViewDidMoveToWindow(); |
| } |
| |
| 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::viewDidHide() |
| { |
| LOG(ActivityState, "WebViewImpl %p (page %llu) viewDidHide", this, m_page->identifier().toUInt64()); |
| m_page->activityStateDidChange(WebCore::ActivityState::IsVisible); |
| } |
| |
| void WebViewImpl::viewDidUnhide() |
| { |
| LOG(ActivityState, "WebViewImpl %p (page %llu) viewDidUnhide", this, m_page->identifier().toUInt64()); |
| m_page->activityStateDidChange(WebCore::ActivityState::IsVisible); |
| } |
| |
| void WebViewImpl::activeSpaceDidChange() |
| { |
| LOG(ActivityState, "WebViewImpl %p (page %llu) activeSpaceDidChange", this, m_page->identifier().toUInt64()); |
| m_page->activityStateDidChange(WebCore::ActivityState::IsVisible); |
| } |
| |
| NSView *WebViewImpl::hitTest(CGPoint point) |
| { |
| NSView *hitView = [m_view _web_superHitTest:NSPointFromCGPoint(point)]; |
| if (hitView && hitView == m_layerHostingView) |
| hitView = m_view.getAutoreleased(); |
| |
| return hitView; |
| } |
| |
| void WebViewImpl::postFakeMouseMovedEventForFlagsChangedEvent(NSEvent *flagsChangedEvent) |
| { |
| NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSEventTypeMouseMoved location:flagsChangedEvent.window.mouseLocationOutsideOfEventStream |
| modifierFlags:flagsChangedEvent.modifierFlags timestamp:flagsChangedEvent.timestamp windowNumber:flagsChangedEvent.windowNumber |
| context:nullptr eventNumber:0 clickCount:0 pressure:0]; |
| NativeWebMouseEvent webEvent(fakeEvent, m_lastPressureEvent.get(), m_view.getAutoreleased()); |
| 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::setUnderlayColor(NSColor *underlayColor) |
| { |
| m_page->setUnderlayColor(WebCore::colorFromNSColor(underlayColor)); |
| } |
| |
| NSColor *WebViewImpl::underlayColor() const |
| { |
| WebCore::Color webColor = m_page->underlayColor(); |
| if (!webColor.isValid()) |
| return nil; |
| |
| return WebCore::nsColor(webColor); |
| } |
| |
| NSColor *WebViewImpl::pageExtendedBackgroundColor() const |
| { |
| WebCore::Color color = m_page->pageExtendedBackgroundColor(); |
| if (!color.isValid()) |
| return nil; |
| |
| return WebCore::nsColor(color); |
| } |
| |
| void WebViewImpl::setOverlayScrollbarStyle(Optional<WebCore::ScrollbarOverlayStyle> scrollbarStyle) |
| { |
| m_page->setOverlayScrollbarStyle(scrollbarStyle); |
| } |
| |
| Optional<WebCore::ScrollbarOverlayStyle> WebViewImpl::overlayScrollbarStyle() const |
| { |
| return m_page->overlayScrollbarStyle(); |
| } |
| |
| 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->activityStateDidChange(WebCore::ActivityState::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->activityStateDidChange(WebCore::ActivityState::IsInWindow); |
| m_viewInWindowChangeWasDeferred = false; |
| } |
| } |
| |
| void WebViewImpl::prepareForMoveToWindow(NSWindow *targetWindow, WTF::Function<void()>&& completionHandler) |
| { |
| m_shouldDeferViewInWindowChanges = true; |
| viewWillMoveToWindow(targetWindow); |
| m_targetWindowForMovePreparation = targetWindow; |
| viewDidMoveToWindow(); |
| |
| m_shouldDeferViewInWindowChanges = false; |
| |
| auto weakThis = makeWeakPtr(*this); |
| m_page->installActivityStateChangeCompletionHandler([weakThis, completionHandler = WTFMove(completionHandler)]() { |
| completionHandler(); |
| |
| if (!weakThis) |
| return; |
| |
| ASSERT(![weakThis->m_view window] || [weakThis->m_view window] == weakThis->m_targetWindowForMovePreparation); |
| weakThis->m_targetWindowForMovePreparation = nil; |
| }); |
| |
| dispatchSetTopContentInset(); |
| m_page->activityStateDidChange(WebCore::ActivityState::IsInWindow, false, WebPageProxy::ActivityStateChangeDispatchMode::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 _web_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.getAutoreleased() != [[m_view window] firstResponder]) |
| return; |
| |
| LOG(TextInput, "-> discardMarkedText"); |
| |
| [[m_view _web_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) |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer)); |
| 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::handleAcceptedAlternativeText(const String& acceptedAlternative) |
| { |
| m_page->handleAlternativeTextUIResult(acceptedAlternative); |
| } |
| |
| |
| NSInteger WebViewImpl::spellCheckerDocumentTag() |
| { |
| if (!m_spellCheckerDocumentTag) |
| m_spellCheckerDocumentTag = [NSSpellChecker uniqueSpellDocumentTag]; |
| return m_spellCheckerDocumentTag.value(); |
| } |
| |
| void WebViewImpl::pressureChangeWithEvent(NSEvent *event) |
| { |
| 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.getAutoreleased()); |
| m_page->handleMouseEvent(webEvent); |
| |
| m_lastPressureEvent = event; |
| } |
| |
| #if ENABLE(FULLSCREEN_API) |
| bool WebViewImpl::hasFullScreenWindowController() const |
| { |
| return !!m_fullScreenWindowController; |
| } |
| |
| WKFullScreenWindowController *WebViewImpl::fullScreenWindowController() |
| { |
| if (!m_fullScreenWindowController) |
| m_fullScreenWindowController = adoptNS([[WKFullScreenWindowController alloc] initWithWindow:fullScreenWindow() webView:m_view.getAutoreleased() 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::fullScreenWindow() |
| { |
| #if ENABLE(FULLSCREEN_API) |
| return [[[WebCoreFullScreenWindow alloc] initWithContentRect:[[NSScreen mainScreen] frame] styleMask:(NSWindowStyleMaskTitled | NSWindowStyleMaskUnifiedTitleAndToolbar | NSWindowStyleMaskFullSizeContentView | NSWindowStyleMaskResizable) backing:NSBackingStoreBuffered defer:NO] autorelease]; |
| #else |
| return nil; |
| #endif |
| } |
| |
| bool WebViewImpl::isEditable() const |
| { |
| return m_page->isEditable(); |
| } |
| |
| typedef HashMap<SEL, String> SelectorNameMap; |
| |
| // Map selectors into Editor command names. |
| // This is not needed for any selectors that have the same name as the Editor command. |
| static const SelectorNameMap& selectorExceptionMap() |
| { |
| static NeverDestroyed<SelectorNameMap> map; |
| |
| struct SelectorAndCommandName { |
| SEL selector; |
| ASCIILiteral commandName; |
| }; |
| |
| static const SelectorAndCommandName names[] = { |
| { @selector(insertNewlineIgnoringFieldEditor:), "InsertNewline"_s }, |
| { @selector(insertParagraphSeparator:), "InsertNewline"_s }, |
| { @selector(insertTabIgnoringFieldEditor:), "InsertTab"_s }, |
| { @selector(pageDown:), "MovePageDown"_s }, |
| { @selector(pageDownAndModifySelection:), "MovePageDownAndModifySelection"_s }, |
| { @selector(pageUp:), "MovePageUp"_s }, |
| { @selector(pageUpAndModifySelection:), "MovePageUpAndModifySelection"_s }, |
| { @selector(scrollPageDown:), "ScrollPageForward"_s }, |
| { @selector(scrollPageUp:), "ScrollPageBackward"_s }, |
| { @selector(_pasteAsQuotation:), "PasteAsQuotation"_s }, |
| }; |
| |
| for (auto& name : names) |
| map.get().add(name.selector, name.commandName); |
| |
| return map; |
| } |
| |
| static String commandNameForSelector(SEL selector) |
| { |
| // Check the exception map first. |
| static const SelectorNameMap& exceptionMap = selectorExceptionMap(); |
| SelectorNameMap::const_iterator it = exceptionMap.find(selector); |
| if (it != exceptionMap.end()) |
| return it->value; |
| |
| // Remove the trailing colon. |
| // No need to capitalize the command name since Editor command names are |
| // not case sensitive. |
| const char* selectorName = sel_getName(selector); |
| size_t selectorNameLength = strlen(selectorName); |
| if (selectorNameLength < 2 || selectorName[selectorNameLength - 1] != ':') |
| return String(); |
| return String(selectorName, selectorNameLength - 1); |
| } |
| |
| bool WebViewImpl::executeSavedCommandBySelector(SEL selector) |
| { |
| LOG(TextInput, "Executing previously saved command %s", sel_getName(selector)); |
| // The sink does two things: 1) Tells us if the responder went unhandled, and |
| // 2) prevents any NSBeep; we don't ever want to beep here. |
| RetainPtr<WKResponderChainSink> sink = adoptNS([[WKResponderChainSink alloc] initWithResponderChain:m_view.getAutoreleased()]); |
| [m_view _web_superDoCommandBySelector:selector]; |
| [sink detach]; |
| return ![sink didReceiveUnhandledCommand]; |
| } |
| |
| void WebViewImpl::executeEditCommandForSelector(SEL selector, const String& argument) |
| { |
| m_page->executeEditCommand(commandNameForSelector(selector), argument); |
| } |
| |
| void WebViewImpl::registerEditCommand(Ref<WebEditCommandProxy>&& command, UndoOrRedo undoOrRedo) |
| { |
| auto actionName = command->label(); |
| auto commandObjC = adoptNS([[WKEditCommand alloc] initWithWebEditCommandProxy:WTFMove(command)]); |
| |
| NSUndoManager *undoManager = [m_view undoManager]; |
| [undoManager registerUndoWithTarget:m_undoTarget.get() selector:((undoOrRedo == UndoOrRedo::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:WebCore::legacyStringPasteboardType()]) |
| [pasteboard setString:m_page->stringSelectionForPasteboard() forType:WebCore::legacyStringPasteboardType()]; |
| else { |
| RefPtr<WebCore::SharedBuffer> buffer = m_page->dataSelectionForPasteboard([types objectAtIndex:i]); |
| [pasteboard setData:buffer ? buffer->createNSData().get() : nil forType:[types objectAtIndex:i]]; |
| } |
| } |
| return true; |
| } |
| |
| bool WebViewImpl::readSelectionFromPasteboard(NSPasteboard *pasteboard) |
| { |
| return m_page->readSelectionFromPasteboard([pasteboard name]); |
| } |
| |
| id WebViewImpl::validRequestorForSendAndReturnTypes(NSString *sendType, NSString *returnType) |
| { |
| EditorState editorState = m_page->editorState(); |
| bool isValidSendType = false; |
| |
| if (sendType && !editorState.selectionIsNone) { |
| if (editorState.isInPlugin) |
| isValidSendType = [sendType isEqualToString:WebCore::legacyStringPasteboardType()]; |
| else |
| isValidSendType = [PasteboardTypes::forSelection() containsObject:sendType]; |
| } |
| |
| bool isValidReturnType = false; |
| if (!returnType) |
| isValidReturnType = true; |
| else if ([PasteboardTypes::forEditing() containsObject:returnType] && editorState.isContentEditable) { |
| // We can insert strings in any editable context. We can insert other types, like images, only in rich edit contexts. |
| isValidReturnType = editorState.isContentRichlyEditable || [returnType isEqualToString:WebCore::legacyStringPasteboardType()]; |
| } |
| if (isValidSendType && isValidReturnType) |
| return m_view.getAutoreleased(); |
| return [[m_view nextResponder] validRequestorForSendType:sendType returnType:returnType]; |
| } |
| |
| void WebViewImpl::centerSelectionInVisibleArea() |
| { |
| m_page->centerSelectionInVisibleArea(); |
| } |
| |
| void WebViewImpl::selectionDidChange() |
| { |
| updateFontManagerIfNeeded(); |
| if (!m_isHandlingAcceptedCandidate) |
| m_softSpaceRange = NSMakeRange(NSNotFound, 0); |
| #if HAVE(TOUCH_BAR) |
| updateTouchBar(); |
| if (!m_page->editorState().isMissingPostLayoutData) |
| requestCandidatesForSelectionIfNeeded(); |
| #endif |
| |
| NSWindow *window = [m_view window]; |
| if (window.firstResponder == m_view.get().get()) { |
| NSInspectorBar *inspectorBar = window.inspectorBar; |
| if (inspectorBar.visible) |
| [inspectorBar _update]; |
| } |
| |
| [m_view _web_editorStateDidChange]; |
| } |
| |
| void WebViewImpl::showShareSheet(const WebCore::ShareDataWithParsedURL& data, WTF::CompletionHandler<void(bool)>&& completionHandler, WKWebView *view) |
| { |
| if (_shareSheet) |
| [_shareSheet dismiss]; |
| |
| ASSERT([view respondsToSelector:@selector(shareSheetDidDismiss:)]); |
| _shareSheet = adoptNS([[WKShareSheet alloc] initWithView:view]); |
| [_shareSheet setDelegate:view]; |
| |
| [_shareSheet presentWithParameters:data inRect:WTF::nullopt completionHandler:WTFMove(completionHandler)]; |
| } |
| |
| void WebViewImpl::shareSheetDidDismiss(WKShareSheet *shareSheet) |
| { |
| ASSERT(_shareSheet == shareSheet); |
| |
| [_shareSheet setDelegate:nil]; |
| _shareSheet = nil; |
| } |
| |
| void WebViewImpl::didBecomeEditable() |
| { |
| [m_windowVisibilityObserver startObservingFontPanel]; |
| |
| dispatch_async(dispatch_get_main_queue(), [] { |
| [[NSSpellChecker sharedSpellChecker] _preflightChosenSpellServer]; |
| }); |
| } |
| |
| void WebViewImpl::updateFontManagerIfNeeded() |
| { |
| BOOL fontPanelIsVisible = NSFontPanel.sharedFontPanelExists && NSFontPanel.sharedFontPanel.visible; |
| if (!fontPanelIsVisible && !m_page->editorState().isContentRichlyEditable) |
| return; |
| |
| m_page->fontAtSelection([](const FontInfo& fontInfo, double fontSize, bool selectionHasMultipleFonts, CallbackBase::Error error) { |
| if (error != CallbackBase::Error::None) |
| return; |
| |
| NSDictionary *attributeDictionary = (__bridge NSDictionary *)fontInfo.fontAttributeDictionary.get(); |
| if (!attributeDictionary) |
| return; |
| |
| NSFontDescriptor *descriptor = [NSFontDescriptor fontDescriptorWithFontAttributes:attributeDictionary]; |
| if (!descriptor) |
| return; |
| |
| NSFont *font = [NSFont fontWithDescriptor:descriptor size:fontSize]; |
| if (!font) |
| return; |
| |
| [NSFontManager.sharedFontManager setSelectedFont:font isMultiple:selectionHasMultipleFonts]; |
| }); |
| } |
| |
| void WebViewImpl::typingAttributesWithCompletionHandler(void(^completion)(NSDictionary<NSString *, id> *)) |
| { |
| if (auto attributes = m_page->cachedFontAttributesAtSelectionStart()) { |
| auto attributesAsDictionary = attributes->createDictionary(); |
| completion(attributesAsDictionary.get()); |
| return; |
| } |
| |
| m_page->requestFontAttributesAtSelectionStart([completion = makeBlockPtr(completion)] (const WebCore::FontAttributes& attributes, CallbackBase::Error error) { |
| if (error != CallbackBase::Error::None) { |
| completion(nil); |
| return; |
| } |
| |
| auto attributesAsDictionary = attributes.createDictionary(); |
| completion(attributesAsDictionary.get()); |
| }); |
| } |
| |
| void WebViewImpl::changeFontColorFromSender(id sender) |
| { |
| if (![sender respondsToSelector:@selector(color)]) |
| return; |
| |
| id color = [sender color]; |
| if (![color isKindOfClass:NSColor.class]) |
| return; |
| |
| auto& editorState = m_page->editorState(); |
| if (!editorState.isContentEditable || editorState.selectionIsNone) |
| return; |
| |
| WebCore::FontAttributeChanges changes; |
| changes.setForegroundColor(WebCore::colorFromNSColor((NSColor *)color)); |
| m_page->changeFontAttributes(WTFMove(changes)); |
| } |
| |
| void WebViewImpl::changeFontAttributesFromSender(id sender) |
| { |
| auto& editorState = m_page->editorState(); |
| if (!editorState.isContentEditable || editorState.selectionIsNone) |
| return; |
| |
| m_page->changeFontAttributes(WebCore::computedFontAttributeChanges(NSFontManager.sharedFontManager, sender)); |
| updateFontManagerIfNeeded(); |
| } |
| |
| void WebViewImpl::changeFontFromFontManager() |
| { |
| auto& editorState = m_page->editorState(); |
| if (!editorState.isContentEditable || editorState.selectionIsNone) |
| return; |
| |
| m_page->changeFont(WebCore::computedFontChanges(NSFontManager.sharedFontManager)); |
| updateFontManagerIfNeeded(); |
| } |
| |
| static NSMenuItem *menuItem(id <NSValidatedUserInterfaceItem> item) |
| { |
| if (![(NSObject *)item isKindOfClass:[NSMenuItem class]]) |
| return nil; |
| return (NSMenuItem *)item; |
| } |
| |
| static NSToolbarItem *toolbarItem(id <NSValidatedUserInterfaceItem> item) |
| { |
| if (![(NSObject *)item isKindOfClass:[NSToolbarItem class]]) |
| return nil; |
| return (NSToolbarItem *)item; |
| } |
| |
| bool WebViewImpl::validateUserInterfaceItem(id <NSValidatedUserInterfaceItem> item) |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer)); |
| SEL action = [item action]; |
| |
| if (action == @selector(showGuessPanel:)) { |
| if (NSMenuItem *menuItem = WebKit::menuItem(item)) |
| [menuItem setTitle:WebCore::contextMenuItemTagShowSpellingPanel(![[[NSSpellChecker sharedSpellChecker] spellingPanel] isVisible])]; |
| return m_page->editorState().isContentEditable; |
| } |
| |
| if (action == @selector(checkSpelling:) || action == @selector(changeSpelling:)) |
| return m_page->editorState().isContentEditable; |
| |
| if (action == @selector(toggleContinuousSpellChecking:)) { |
| bool enabled = TextChecker::isContinuousSpellCheckingAllowed(); |
| bool checked = enabled && TextChecker::state().isContinuousSpellCheckingEnabled; |
| [menuItem(item) setState:checked ? NSControlStateValueOn : NSControlStateValueOff]; |
| return enabled; |
| } |
| |
| if (action == @selector(toggleGrammarChecking:)) { |
| bool checked = TextChecker::state().isGrammarCheckingEnabled; |
| [menuItem(item) setState:checked ? NSControlStateValueOn : NSControlStateValueOff]; |
| return true; |
| } |
| |
| if (action == @selector(toggleAutomaticSpellingCorrection:)) { |
| bool checked = TextChecker::state().isAutomaticSpellingCorrectionEnabled; |
| [menuItem(item) setState:checked ? NSControlStateValueOn : NSControlStateValueOff]; |
| return m_page->editorState().isContentEditable; |
| } |
| |
| if (action == @selector(orderFrontSubstitutionsPanel:)) { |
| if (NSMenuItem *menuItem = WebKit::menuItem(item)) |
| [menuItem setTitle:WebCore::contextMenuItemTagShowSubstitutions(![[[NSSpellChecker sharedSpellChecker] substitutionsPanel] isVisible])]; |
| return m_page->editorState().isContentEditable; |
| } |
| |
| if (action == @selector(toggleSmartInsertDelete:)) { |
| bool checked = m_page->isSmartInsertDeleteEnabled(); |
| [menuItem(item) setState:checked ? NSControlStateValueOn : NSControlStateValueOff]; |
| return m_page->editorState().isContentEditable; |
| } |
| |
| if (action == @selector(toggleAutomaticQuoteSubstitution:)) { |
| bool checked = TextChecker::state().isAutomaticQuoteSubstitutionEnabled; |
| [menuItem(item) setState:checked ? NSControlStateValueOn : NSControlStateValueOff]; |
| return m_page->editorState().isContentEditable; |
| } |
| |
| if (action == @selector(toggleAutomaticDashSubstitution:)) { |
| bool checked = TextChecker::state().isAutomaticDashSubstitutionEnabled; |
| [menuItem(item) setState:checked ? NSControlStateValueOn : NSControlStateValueOff]; |
| return m_page->editorState().isContentEditable; |
| } |
| |
| if (action == @selector(toggleAutomaticLinkDetection:)) { |
| bool checked = TextChecker::state().isAutomaticLinkDetectionEnabled; |
| [menuItem(item) setState:checked ? NSControlStateValueOn : NSControlStateValueOff]; |
| return m_page->editorState().isContentEditable; |
| } |
| |
| if (action == @selector(toggleAutomaticTextReplacement:)) { |
| bool checked = TextChecker::state().isAutomaticTextReplacementEnabled; |
| [menuItem(item) setState:checked ? NSControlStateValueOn : NSControlStateValueOff]; |
| return m_page->editorState().isContentEditable; |
| } |
| |
| if (action == @selector(uppercaseWord:) || action == @selector(lowercaseWord:) || action == @selector(capitalizeWord:)) |
| return m_page->editorState().selectionIsRange && m_page->editorState().isContentEditable; |
| |
| if (action == @selector(stopSpeaking:)) |
| return [NSApp isSpeaking]; |
| |
| // The centerSelectionInVisibleArea: selector is enabled if there's a selection range or if there's an insertion point in an editable area. |
| if (action == @selector(centerSelectionInVisibleArea:)) |
| return m_page->editorState().selectionIsRange || (m_page->editorState().isContentEditable && !m_page->editorState().selectionIsNone); |
| |
| // Next, handle editor commands. Start by returning true for anything that is not an editor command. |
| // Returning true is the default thing to do in an AppKit validate method for any selector that is not recognized. |
| String commandName = commandNameForSelector([item action]); |
| if (!WebCore::Editor::commandIsSupportedFromMenuOrKeyBinding(commandName)) |
| return true; |
| |
| // Add this item to the vector of items for a given command that are awaiting validation. |
| ValidationMap::AddResult addResult = m_validationMap.add(commandName, ValidationVector()); |
| addResult.iterator->value.append(item); |
| if (addResult.isNewEntry) { |
| // If we are not already awaiting validation for this command, start the asynchronous validation process. |
| // FIXME: Theoretically, there is a race here; when we get the answer it might be old, from a previous time |
| // we asked for the same command; there is no guarantee the answer is still valid. |
| auto weakThis = makeWeakPtr(*this); |
| m_page->validateCommand(commandName, [weakThis](const String& commandName, bool isEnabled, int32_t state, WebKit::CallbackBase::Error error) { |
| if (!weakThis) |
| return; |
| |
| // If the process exits before the command can be validated, we'll be called back with an error. |
| if (error != WebKit::CallbackBase::Error::None) |
| return; |
| |
| weakThis->setUserInterfaceItemState(commandName, isEnabled, state); |
| }); |
| } |
| |
| // Treat as enabled until we get the result back from the web process and _setUserInterfaceItemState is called. |
| // FIXME <rdar://problem/8803459>: This means disabled items will flash enabled at first for a moment. |
| // But returning NO here would be worse; that would make keyboard commands such as command-C fail. |
| return true; |
| } |
| |
| void WebViewImpl::setUserInterfaceItemState(NSString *commandName, bool enabled, int state) |
| { |
| ValidationVector items = m_validationMap.take(commandName); |
| for (auto& item : items) { |
| [menuItem(item.get()) setState:state]; |
| [menuItem(item.get()) setEnabled:enabled]; |
| [toolbarItem(item.get()) setEnabled:enabled]; |
| // FIXME <rdar://problem/8803392>: If the item is neither a menu nor toolbar item, it will be left enabled. |
| } |
| } |
| |
| void WebViewImpl::startSpeaking() |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer)); |
| m_page->getSelectionOrContentsAsString([](const String& string, WebKit::CallbackBase::Error error) { |
| if (error != WebKit::CallbackBase::Error::None) |
| return; |
| if (!string) |
| return; |
| |
| [NSApp speakString:string]; |
| }); |
| } |
| |
| void WebViewImpl::stopSpeaking(id sender) |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer)); |
| [NSApp stopSpeaking:sender]; |
| } |
| |
| void WebViewImpl::showGuessPanel(id sender) |
| { |
| NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker]; |
| if (!checker) { |
| LOG_ERROR("No NSSpellChecker"); |
| return; |
| } |
| |
| NSPanel *spellingPanel = [checker spellingPanel]; |
| if ([spellingPanel isVisible]) { |
| [spellingPanel orderOut:sender]; |
| return; |
| } |
| |
| m_page->advanceToNextMisspelling(true); |
| [spellingPanel orderFront:sender]; |
| } |
| |
| void WebViewImpl::checkSpelling() |
| { |
| m_page->advanceToNextMisspelling(false); |
| } |
| |
| void WebViewImpl::changeSpelling(id sender) |
| { |
| NSString *word = [[sender selectedCell] stringValue]; |
| |
| m_page->changeSpellingToWord(word); |
| } |
| |
| void WebViewImpl::toggleContinuousSpellChecking() |
| { |
| bool spellCheckingEnabled = !TextChecker::state().isContinuousSpellCheckingEnabled; |
| TextChecker::setContinuousSpellCheckingEnabled(spellCheckingEnabled); |
| |
| m_page->process().updateTextCheckerState(); |
| } |
| |
| bool WebViewImpl::isGrammarCheckingEnabled() |
| { |
| return TextChecker::state().isGrammarCheckingEnabled; |
| } |
| |
| void WebViewImpl::setGrammarCheckingEnabled(bool flag) |
| { |
| if (static_cast<bool>(flag) == TextChecker::state().isGrammarCheckingEnabled) |
| return; |
| |
| TextChecker::setGrammarCheckingEnabled(flag); |
| m_page->process().updateTextCheckerState(); |
| } |
| |
| void WebViewImpl::toggleGrammarChecking() |
| { |
| bool grammarCheckingEnabled = !TextChecker::state().isGrammarCheckingEnabled; |
| TextChecker::setGrammarCheckingEnabled(grammarCheckingEnabled); |
| |
| m_page->process().updateTextCheckerState(); |
| } |
| |
| void WebViewImpl::toggleAutomaticSpellingCorrection() |
| { |
| TextChecker::setAutomaticSpellingCorrectionEnabled(!TextChecker::state().isAutomaticSpellingCorrectionEnabled); |
| |
| m_page->process().updateTextCheckerState(); |
| } |
| |
| void WebViewImpl::orderFrontSubstitutionsPanel(id sender) |
| { |
| NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker]; |
| if (!checker) { |
| LOG_ERROR("No NSSpellChecker"); |
| return; |
| } |
| |
| NSPanel *substitutionsPanel = [checker substitutionsPanel]; |
| if ([substitutionsPanel isVisible]) { |
| [substitutionsPanel orderOut:sender]; |
| return; |
| } |
| [substitutionsPanel orderFront:sender]; |
| } |
| |
| void WebViewImpl::toggleSmartInsertDelete() |
| { |
| m_page->setSmartInsertDeleteEnabled(!m_page->isSmartInsertDeleteEnabled()); |
| } |
| |
| bool WebViewImpl::isAutomaticQuoteSubstitutionEnabled() |
| { |
| return TextChecker::state().isAutomaticQuoteSubstitutionEnabled; |
| } |
| |
| void WebViewImpl::setAutomaticQuoteSubstitutionEnabled(bool flag) |
| { |
| if (static_cast<bool>(flag) == TextChecker::state().isAutomaticQuoteSubstitutionEnabled) |
| return; |
| |
| TextChecker::setAutomaticQuoteSubstitutionEnabled(flag); |
| m_page->process().updateTextCheckerState(); |
| } |
| |
| void WebViewImpl::toggleAutomaticQuoteSubstitution() |
| { |
| TextChecker::setAutomaticQuoteSubstitutionEnabled(!TextChecker::state().isAutomaticQuoteSubstitutionEnabled); |
| m_page->process().updateTextCheckerState(); |
| } |
| |
| bool WebViewImpl::isAutomaticDashSubstitutionEnabled() |
| { |
| return TextChecker::state().isAutomaticDashSubstitutionEnabled; |
| } |
| |
| void WebViewImpl::setAutomaticDashSubstitutionEnabled(bool flag) |
| { |
| if (static_cast<bool>(flag) == TextChecker::state().isAutomaticDashSubstitutionEnabled) |
| return; |
| |
| TextChecker::setAutomaticDashSubstitutionEnabled(flag); |
| m_page->process().updateTextCheckerState(); |
| } |
| |
| void WebViewImpl::toggleAutomaticDashSubstitution() |
| { |
| TextChecker::setAutomaticDashSubstitutionEnabled(!TextChecker::state().isAutomaticDashSubstitutionEnabled); |
| m_page->process().updateTextCheckerState(); |
| } |
| |
| bool WebViewImpl::isAutomaticLinkDetectionEnabled() |
| { |
| return TextChecker::state().isAutomaticLinkDetectionEnabled; |
| } |
| |
| void WebViewImpl::setAutomaticLinkDetectionEnabled(bool flag) |
| { |
| if (static_cast<bool>(flag) == TextChecker::state().isAutomaticLinkDetectionEnabled) |
| return; |
| |
| TextChecker::setAutomaticLinkDetectionEnabled(flag); |
| m_page->process().updateTextCheckerState(); |
| } |
| |
| void WebViewImpl::toggleAutomaticLinkDetection() |
| { |
| TextChecker::setAutomaticLinkDetectionEnabled(!TextChecker::state().isAutomaticLinkDetectionEnabled); |
| m_page->process().updateTextCheckerState(); |
| } |
| |
| bool WebViewImpl::isAutomaticTextReplacementEnabled() |
| { |
| return TextChecker::state().isAutomaticTextReplacementEnabled; |
| } |
| |
| void WebViewImpl::setAutomaticTextReplacementEnabled(bool flag) |
| { |
| if (static_cast<bool>(flag) == TextChecker::state().isAutomaticTextReplacementEnabled) |
| return; |
| |
| TextChecker::setAutomaticTextReplacementEnabled(flag); |
| m_page->process().updateTextCheckerState(); |
| } |
| |
| void WebViewImpl::toggleAutomaticTextReplacement() |
| { |
| TextChecker::setAutomaticTextReplacementEnabled(!TextChecker::state().isAutomaticTextReplacementEnabled); |
| m_page->process().updateTextCheckerState(); |
| } |
| |
| void WebViewImpl::uppercaseWord() |
| { |
| m_page->uppercaseWord(); |
| } |
| |
| void WebViewImpl::lowercaseWord() |
| { |
| m_page->lowercaseWord(); |
| } |
| |
| void WebViewImpl::capitalizeWord() |
| { |
| m_page->capitalizeWord(); |
| } |
| |
| void WebViewImpl::requestCandidatesForSelectionIfNeeded() |
| { |
| if (!shouldRequestCandidates()) |
| return; |
| |
| const EditorState& editorState = m_page->editorState(); |
| if (!editorState.isContentEditable) |
| return; |
| |
| if (editorState.isMissingPostLayoutData) |
| return; |
| |
| auto& postLayoutData = editorState.postLayoutData(); |
| m_lastStringForCandidateRequest = postLayoutData.stringForCandidateRequest; |
| |
| NSRange selectedRange = NSMakeRange(postLayoutData.candidateRequestStartPosition, postLayoutData.selectedTextLength); |
| NSTextCheckingTypes checkingTypes = NSTextCheckingTypeSpelling | NSTextCheckingTypeReplacement | NSTextCheckingTypeCorrection; |
| auto weakThis = makeWeakPtr(*this); |
| m_lastCandidateRequestSequenceNumber = [[NSSpellChecker sharedSpellChecker] requestCandidatesForSelectedRange:selectedRange inString:postLayoutData.paragraphContextForCandidateRequest types:checkingTypes options:nil inSpellDocumentWithTag:spellCheckerDocumentTag() completionHandler:[weakThis](NSInteger sequenceNumber, NSArray<NSTextCheckingResult *> *candidates) { |
| dispatch_async(dispatch_get_main_queue(), ^{ |
| if (!weakThis) |
| return; |
| weakThis->handleRequestedCandidates(sequenceNumber, candidates); |
| }); |
| }]; |
| } |
| |
| void WebViewImpl::handleRequestedCandidates(NSInteger sequenceNumber, NSArray<NSTextCheckingResult *> *candidates) |
| { |
| if (!shouldRequestCandidates()) |
| return; |
| |
| if (m_lastCandidateRequestSequenceNumber != sequenceNumber) |
| return; |
| |
| const EditorState& editorState = m_page->editorState(); |
| if (!editorState.isContentEditable) |
| return; |
| |
| // FIXME: It's pretty lame that we have to depend on the most recent EditorState having post layout data, |
| // and that we just bail if it is missing. |
| if (editorState.isMissingPostLayoutData) |
| return; |
| |
| auto& postLayoutData = editorState.postLayoutData(); |
| if (m_lastStringForCandidateRequest != postLayoutData.stringForCandidateRequest) |
| return; |
| |
| #if HAVE(TOUCH_BAR) |
| NSRange selectedRange = NSMakeRange(postLayoutData.candidateRequestStartPosition, postLayoutData.selectedTextLength); |
| WebCore::IntRect offsetSelectionRect = postLayoutData.focusedElementRect; |
| offsetSelectionRect.move(0, offsetSelectionRect.height()); |
| |
| [candidateListTouchBarItem() setCandidates:candidates forSelectedRange:selectedRange inString:postLayoutData.paragraphContextForCandidateRequest rect:offsetSelectionRect view:m_view.getAutoreleased() completionHandler:nil]; |
| #else |
| UNUSED_PARAM(candidates); |
| #endif |
| } |
| |
| static constexpr WebCore::TextCheckingType coreTextCheckingType(NSTextCheckingType type) |
| { |
| switch (type) { |
| case NSTextCheckingTypeCorrection: |
| return WebCore::TextCheckingType::Correction; |
| case NSTextCheckingTypeReplacement: |
| return WebCore::TextCheckingType::Replacement; |
| case NSTextCheckingTypeSpelling: |
| return WebCore::TextCheckingType::Spelling; |
| default: |
| return WebCore::TextCheckingType::None; |
| } |
| } |
| |
| static WebCore::TextCheckingResult textCheckingResultFromNSTextCheckingResult(NSTextCheckingResult *nsResult) |
| { |
| NSRange resultRange = [nsResult range]; |
| |
| WebCore::TextCheckingResult result; |
| result.type = coreTextCheckingType(nsResult.resultType); |
| result.location = resultRange.location; |
| result.length = resultRange.length; |
| result.replacement = nsResult.replacementString; |
| return result; |
| } |
| |
| void WebViewImpl::handleAcceptedCandidate(NSTextCheckingResult *acceptedCandidate) |
| { |
| const EditorState& editorState = m_page->editorState(); |
| if (!editorState.isContentEditable) |
| return; |
| |
| // FIXME: It's pretty lame that we have to depend on the most recent EditorState having post layout data, |
| // and that we just bail if it is missing. |
| if (editorState.isMissingPostLayoutData) |
| return; |
| |
| auto& postLayoutData = editorState.postLayoutData(); |
| if (m_lastStringForCandidateRequest != postLayoutData.stringForCandidateRequest) |
| return; |
| |
| m_isHandlingAcceptedCandidate = true; |
| NSRange range = [acceptedCandidate range]; |
| if (acceptedCandidate.replacementString && [acceptedCandidate.replacementString length] > 0) { |
| NSRange replacedRange = NSMakeRange(range.location, [acceptedCandidate.replacementString length]); |
| NSRange softSpaceRange = NSMakeRange(NSMaxRange(replacedRange) - 1, 1); |
| if ([acceptedCandidate.replacementString hasSuffix:@" "]) |
| m_softSpaceRange = softSpaceRange; |
| } |
| |
| m_page->handleAcceptedCandidate(textCheckingResultFromNSTextCheckingResult(acceptedCandidate)); |
| } |
| |
| void WebViewImpl::doAfterProcessingAllPendingMouseEvents(dispatch_block_t action) |
| { |
| if (!m_page->isProcessingMouseEvents()) { |
| action(); |
| return; |
| } |
| |
| m_callbackHandlersAfterProcessingPendingMouseEvents.append(makeBlockPtr(action)); |
| } |
| |
| void WebViewImpl::didFinishProcessingAllPendingMouseEvents() |
| { |
| flushPendingMouseEventCallbacks(); |
| } |
| |
| void WebViewImpl::flushPendingMouseEventCallbacks() |
| { |
| for (auto& callback : m_callbackHandlersAfterProcessingPendingMouseEvents) |
| callback(); |
| |
| m_callbackHandlersAfterProcessingPendingMouseEvents.clear(); |
| } |
| |
| 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 = makeUnique<WebCore::TextIndicatorWindow>(m_view.getAutoreleased()); |
| |
| 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 _web_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 _web_dismissContentRelativeChildWindows]; |
| } |
| |
| void WebViewImpl::dismissContentRelativeChildWindowsFromViewOnly() |
| { |
| bool hasActiveImmediateAction = false; |
| hasActiveImmediateAction = [m_immediateActionController hasActiveImmediateAction]; |
| |
| // 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 (DataDetectorsLibrary()) |
| [[getDDActionsManagerClass() sharedManager] requestBubbleClosureUnanchorOnFailure:YES]; |
| } |
| |
| clearTextIndicatorWithAnimation(WebCore::TextIndicatorWindowDismissalAnimation::FadeOut); |
| |
| [m_immediateActionController dismissContentRelativeChildWindows]; |
| |
| m_pageClient->dismissCorrectionPanel(WebCore::ReasonForDismissingAlternativeTextIgnored); |
| } |
| |
| void WebViewImpl::hideWordDefinitionWindow() |
| { |
| WebCore::DictionaryLookup::hidePopup(); |
| } |
| |
| void WebViewImpl::quickLookWithEvent(NSEvent *event) |
| { |
| if (ignoresNonWheelEvents()) |
| return; |
| |
| if (m_immediateActionGestureRecognizer) { |
| [m_view _web_superQuickLookWithEvent:event]; |
| return; |
| } |
| |
| NSPoint locationInViewCoordinates = [m_view convertPoint:[event locationInWindow] fromView:nil]; |
| m_page->performDictionaryLookupAtLocation(WebCore::FloatPoint(locationInViewCoordinates)); |
| } |
| |
| void WebViewImpl::prepareForDictionaryLookup() |
| { |
| [m_windowVisibilityObserver startObservingLookupDismissalIfNeeded]; |
| } |
| |
| void WebViewImpl::setAllowsLinkPreview(bool allowsLinkPreview) |
| { |
| if (m_allowsLinkPreview == allowsLinkPreview) |
| return; |
| |
| m_allowsLinkPreview = allowsLinkPreview; |
| |
| if (!allowsLinkPreview) |
| [m_view removeGestureRecognizer:m_immediateActionGestureRecognizer.get()]; |
| else if (NSGestureRecognizer *immediateActionRecognizer = m_immediateActionGestureRecognizer.get()) |
| [m_view addGestureRecognizer:immediateActionRecognizer]; |
| } |
| |
| NSObject *WebViewImpl::immediateActionAnimationControllerForHitTestResult(API::HitTestResult* hitTestResult, uint32_t type, API::Object* userData) |
| { |
| return [m_view _web_immediateActionAnimationControllerForHitTestResultInternal:hitTestResult withType:type userData:userData]; |
| } |
| |
| void WebViewImpl::didPerformImmediateActionHitTest(const WebHitTestResultData& result, bool contentPreventsDefault, API::Object* userData) |
| { |
| [m_immediateActionController didPerformImmediateActionHitTest:result contentPreventsDefault:contentPreventsDefault userData:userData]; |
| } |
| |
| void WebViewImpl::prepareForImmediateActionAnimation() |
| { |
| [m_view _web_prepareForImmediateActionAnimation]; |
| } |
| |
| void WebViewImpl::cancelImmediateActionAnimation() |
| { |
| [m_view _web_cancelImmediateActionAnimation]; |
| } |
| |
| void WebViewImpl::completeImmediateActionAnimation() |
| { |
| [m_view _web_completeImmediateActionAnimation]; |
| } |
| |
| void WebViewImpl::didChangeContentSize(CGSize newSize) |
| { |
| [m_view _web_didChangeContentSize:NSSizeFromCGSize(newSize)]; |
| } |
| |
| void WebViewImpl::didHandleAcceptedCandidate() |
| { |
| m_isHandlingAcceptedCandidate = false; |
| |
| [m_view _didHandleAcceptedCandidate]; |
| } |
| |
| void WebViewImpl::videoControlsManagerDidChange() |
| { |
| #if HAVE(TOUCH_BAR) |
| updateTouchBar(); |
| #endif |
| |
| #if ENABLE(FULLSCREEN_API) |
| if (hasFullScreenWindowController()) |
| [fullScreenWindowController() videoControlsManagerDidChange]; |
| #endif |
| } |
| |
| void WebViewImpl::setIgnoresNonWheelEvents(bool ignoresNonWheelEvents) |
| { |
| if (m_ignoresNonWheelEvents == ignoresNonWheelEvents) |
| return; |
| |
| m_ignoresNonWheelEvents = ignoresNonWheelEvents; |
| m_page->setShouldDispatchFakeMouseMoveEvents(!ignoresNonWheelEvents); |
| |
| if (ignoresNonWheelEvents) |
| [m_view removeGestureRecognizer:m_immediateActionGestureRecognizer.get()]; |
| else if (NSGestureRecognizer *immediateActionRecognizer = m_immediateActionGestureRecognizer.get()) { |
| if (m_allowsLinkPreview) |
| [m_view addGestureRecognizer:immediateActionRecognizer]; |
| } |
| } |
| |
| 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 = data.length ? adoptNS([[NSAccessibilityRemoteUIElement alloc] initWithRemoteToken:data]) : nil; |
| 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 = [m_remoteAccessibilityChild processIdentifier]; |
| m_remoteAccessibilityChild = nil; |
| } |
| if (!pid) |
| return; |
| |
| if (registerProcess) |
| [NSAccessibilityRemoteUIElement registerRemoteUIProcessIdentifier:pid]; |
| else |
| [NSAccessibilityRemoteUIElement unregisterRemoteUIProcessIdentifier:pid]; |
| } |
| |
| void WebViewImpl::accessibilityRegisterUIProcessTokens() |
| { |
| // Initialize remote accessibility when the window connection has been established. |
| NSData *remoteElementToken = [NSAccessibilityRemoteUIElement remoteTokenForLocalUIElement:m_view.getAutoreleased()]; |
| NSData *remoteWindowToken = [NSAccessibilityRemoteUIElement remoteTokenForLocalUIElement:[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, id parameter) |
| { |
| 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; |
| |
| if ([attribute isEqualToString:@"AXConvertRelativeFrame"]) { |
| if ([parameter isKindOfClass:[NSValue class]]) { |
| NSRect rect = [(NSValue *)parameter rectValue]; |
| return [NSValue valueWithRect:m_pageClient->rootViewToScreen(WebCore::IntRect(rect))]; |
| } |
| } |
| |
| return [m_view _web_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 _web_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:NSEventTypeMouseExited |
| 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:NSEventTypeMouseEntered |
| 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.getAutoreleased() userData:NULL]; |
| sendToolTipMouseEntered(); |
| } |
| } |
| |
| void WebViewImpl::setAcceleratedCompositingRootLayer(CALayer *rootLayer) |
| { |
| [rootLayer web_disableAllActions]; |
| |
| m_rootLayer = rootLayer; |
| |
| if (m_thumbnailView) { |
| updateThumbnailViewLayer(); |
| return; |
| } |
| |
| [CATransaction begin]; |
| [CATransaction setDisableActions:YES]; |
| |
| [m_layerHostingView layer].sublayers = rootLayer ? @[ rootLayer ] : nil; |
| |
| [CATransaction commit]; |
| } |
| |
| 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(); |
| } |
| |
| void WebViewImpl::setInspectorAttachmentView(NSView *newView) |
| { |
| NSView *oldView = m_inspectorAttachmentView.get(); |
| if (oldView == newView) |
| return; |
| |
| m_inspectorAttachmentView = newView; |
| m_page->inspector()->attachmentViewDidChange(oldView ? oldView : m_view.getAutoreleased(), newView ? newView : m_view.getAutoreleased()); |
| } |
| |
| NSView *WebViewImpl::inspectorAttachmentView() |
| { |
| NSView *attachmentView = m_inspectorAttachmentView.get(); |
| return attachmentView ? attachmentView : m_view.getAutoreleased(); |
| } |
| |
| _WKRemoteObjectRegistry *WebViewImpl::remoteObjectRegistry() |
| { |
| if (!m_remoteObjectRegistry) { |
| m_remoteObjectRegistry = adoptNS([[_WKRemoteObjectRegistry alloc] _initWithWebPageProxy:m_page]); |
| m_page->process().processPool().addMessageReceiver(Messages::RemoteObjectRegistry::messageReceiverName(), m_page->identifier(), [m_remoteObjectRegistry remoteObjectRegistry]); |
| } |
| |
| return m_remoteObjectRegistry.get(); |
| } |
| |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| WKBrowsingContextController *WebViewImpl::browsingContextController() |
| { |
| if (!m_browsingContextController) |
| m_browsingContextController = adoptNS([[WKBrowsingContextController alloc] _initWithPageRef:toAPI(m_page.ptr())]); |
| |
| return m_browsingContextController.get(); |
| } |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| |
| #if ENABLE(DRAG_SUPPORT) |
| void WebViewImpl::draggedImage(NSImage *, CGPoint endPoint, NSDragOperation operation) |
| { |
| sendDragEndToPage(endPoint, operation); |
| } |
| |
| void WebViewImpl::sendDragEndToPage(CGPoint endPoint, NSDragOperation operation) |
| { |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| NSPoint windowImageLoc = [[m_view window] convertScreenToBase:NSPointFromCGPoint(endPoint)]; |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| 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::IntPoint(WebCore::globalPoint(windowMouseLoc, [m_view window])), operation); |
| } |
| |
| static WebCore::DragApplicationFlags applicationFlagsForDrag(NSView *view, id <NSDraggingInfo> draggingInfo) |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer)); |
| 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 & NSEventModifierFlagOption) |
| 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])); |
| auto dragDestinationAction = static_cast<WebCore::DragDestinationAction>([m_view _web_dragDestinationActionForDraggingInfo:draggingInfo]); |
| WebCore::DragData dragData(draggingInfo, client, global, static_cast<WebCore::DragOperation>(draggingInfo.draggingSourceOperationMask), applicationFlagsForDrag(m_view.getAutoreleased(), draggingInfo), dragDestinationAction); |
| |
| m_page->resetCurrentDragInformation(); |
| m_page->dragEntered(dragData, draggingInfo.draggingPasteboard.name); |
| m_initialNumberOfValidItemsForDrop = draggingInfo.numberOfValidItemsForDrop; |
| 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])); |
| auto dragDestinationAction = static_cast<WebCore::DragDestinationAction>([m_view _web_dragDestinationActionForDraggingInfo:draggingInfo]); |
| WebCore::DragData dragData(draggingInfo, client, global, static_cast<WebCore::DragOperation>(draggingInfo.draggingSourceOperationMask), applicationFlagsForDrag(m_view.getAutoreleased(), draggingInfo), dragDestinationAction); |
| m_page->dragUpdated(dragData, draggingInfo.draggingPasteboard.name); |
| |
| NSInteger numberOfValidItemsForDrop = m_page->currentDragNumberOfFilesToBeAccepted(); |
| |
| if (m_page->currentDragOperation() == WebCore::DragOperationNone) |
| numberOfValidItemsForDrop = m_initialNumberOfValidItemsForDrop; |
| |
| 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.getAutoreleased(), draggingInfo)); |
| m_page->dragExited(dragData, draggingInfo.draggingPasteboard.name); |
| m_page->resetCurrentDragInformation(); |
| draggingInfo.numberOfValidItemsForDrop = m_initialNumberOfValidItemsForDrop; |
| m_initialNumberOfValidItemsForDrop = 0; |
| } |
| |
| bool WebViewImpl::prepareForDragOperation(id <NSDraggingInfo>) |
| { |
| return true; |
| } |
| |
| 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 = new WebCore::DragData(draggingInfo, client, global, static_cast<WebCore::DragOperation>(draggingInfo.draggingSourceOperationMask), applicationFlagsForDrag(m_view.getAutoreleased(), draggingInfo)); |
| |
| NSArray *types = draggingInfo.draggingPasteboard.types; |
| SandboxExtension::Handle sandboxExtensionHandle; |
| SandboxExtension::HandleArray sandboxExtensionForUpload; |
| |
| if (![types containsObject:PasteboardTypes::WebArchivePboardType] && [types containsObject:WebCore::legacyFilesPromisePasteboardType()]) { |
| |
| // FIXME: legacyFilesPromisePasteboardType() contains UTIs, not path names. Also, it's not |
| // guaranteed that the count of UTIs equals the count of files, since some clients only write |
| // unique UTIs. |
| NSArray *files = [draggingInfo.draggingPasteboard propertyListForType:WebCore::legacyFilesPromisePasteboardType()]; |
| if (![files isKindOfClass:[NSArray class]]) { |
| delete dragData; |
| return false; |
| } |
| |
| NSString *dropDestinationPath = FileSystem::createTemporaryDirectory(@"WebKitDropDestination"); |
| if (!dropDestinationPath) { |
| delete dragData; |
| return false; |
| } |
| |
| size_t fileCount = files.count; |
| Vector<String> *fileNames = new Vector<String>; |
| NSURL *dropDestination = [NSURL fileURLWithPath:dropDestinationPath isDirectory:YES]; |
| String pasteboardName = draggingInfo.draggingPasteboard.name; |
| [draggingInfo enumerateDraggingItemsWithOptions:0 forView:m_view.getAutoreleased() classes:@[[NSFilePromiseReceiver class]] searchOptions:@{ } usingBlock:^(NSDraggingItem *draggingItem, NSInteger idx, BOOL *stop) { |
| NSFilePromiseReceiver *item = draggingItem.item; |
| NSDictionary *options = @{ }; |
| |
| RetainPtr<NSOperationQueue> queue = adoptNS([NSOperationQueue new]); |
| [item receivePromisedFilesAtDestination:dropDestination options:options operationQueue:queue.get() reader:^(NSURL *fileURL, NSError *errorOrNil) { |
| if (errorOrNil) |
| return; |
| |
| dispatch_async(dispatch_get_main_queue(), [this, path = RetainPtr<NSString>(fileURL.path), fileNames, fileCount, dragData, pasteboardName] { |
| fileNames->append(path.get()); |
| if (fileNames->size() == fileCount) { |
| SandboxExtension::Handle sandboxExtensionHandle; |
| SandboxExtension::HandleArray sandboxExtensionForUpload; |
| |
| m_page->createSandboxExtensionsIfNeeded(*fileNames, sandboxExtensionHandle, sandboxExtensionForUpload); |
| dragData->setFileNames(*fileNames); |
| m_page->performDragOperation(*dragData, pasteboardName, WTFMove(sandboxExtensionHandle), WTFMove(sandboxExtensionForUpload)); |
| delete dragData; |
| delete fileNames; |
| } |
| }); |
| }]; |
| }]; |
| |
| return true; |
| } |
| |
| if ([types containsObject:WebCore::legacyFilenamesPasteboardType()]) { |
| NSArray *files = [draggingInfo.draggingPasteboard propertyListForType:WebCore::legacyFilenamesPasteboardType()]; |
| if (![files isKindOfClass:[NSArray class]]) { |
| delete dragData; |
| return false; |
| } |
| |
| Vector<String> fileNames; |
| |
| for (NSString *file in files) |
| fileNames.append(file); |
| m_page->createSandboxExtensionsIfNeeded(fileNames, sandboxExtensionHandle, sandboxExtensionForUpload); |
| } |
| |
| m_page->performDragOperation(*dragData, draggingInfo.draggingPasteboard.name, WTFMove(sandboxExtensionHandle), WTFMove(sandboxExtensionForUpload)); |
| delete dragData; |
| |
| 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.getAutoreleased(); |
| return nil; |
| } |
| |
| void WebViewImpl::registerDraggedTypes() |
| { |
| auto types = adoptNS([[NSMutableSet alloc] initWithArray:PasteboardTypes::forEditing()]); |
| [types addObjectsFromArray:PasteboardTypes::forURL()]; |
| [types addObject:PasteboardTypes::WebDummyPboardType]; |
| [m_view registerForDraggedTypes:[types allObjects]]; |
| } |
| |
| NSString *WebViewImpl::fileNameForFilePromiseProvider(NSFilePromiseProvider *provider, NSString *) |
| { |
| id userInfo = provider.userInfo; |
| if (![userInfo isKindOfClass:[WKPromisedAttachmentContext class]]) |
| return nil; |
| |
| return [(WKPromisedAttachmentContext *)userInfo fileName]; |
| } |
| |
| static NSError *webKitUnknownError() |
| { |
| return [NSError errorWithDomain:WKErrorDomain code:WKErrorUnknown userInfo:nil]; |
| } |
| |
| void WebViewImpl::didPerformDragOperation(bool handled) |
| { |
| [m_view _web_didPerformDragOperation:handled]; |
| } |
| |
| void WebViewImpl::writeToURLForFilePromiseProvider(NSFilePromiseProvider *provider, NSURL *fileURL, void(^completionHandler)(NSError *)) |
| { |
| id userInfo = provider.userInfo; |
| if (![userInfo isKindOfClass:[WKPromisedAttachmentContext class]]) { |
| completionHandler(webKitUnknownError()); |
| return; |
| } |
| |
| WKPromisedAttachmentContext *info = (WKPromisedAttachmentContext *)userInfo; |
| auto attachment = m_page->attachmentForIdentifier(info.attachmentIdentifier); |
| if (NSFileWrapper *fileWrapper = attachment ? attachment->fileWrapper() : nil) { |
| NSError *attachmentWritingError = nil; |
| if ([fileWrapper writeToURL:fileURL options:0 originalContentsURL:nil error:&attachmentWritingError]) |
| completionHandler(nil); |
| else |
| completionHandler(attachmentWritingError); |
| return; |
| } |
| |
| URL blobURL { info.blobURL }; |
| if (blobURL.isEmpty()) { |
| completionHandler(webKitUnknownError()); |
| return; |
| } |
| |
| completionHandler(webKitUnknownError()); |
| } |
| |
| NSDragOperation WebViewImpl::dragSourceOperationMask(NSDraggingSession *, NSDraggingContext context) |
| { |
| if (context == NSDraggingContextOutsideApplication || m_page->currentDragIsOverFileInput()) |
| return NSDragOperationCopy; |
| return NSDragOperationGeneric | NSDragOperationMove | NSDragOperationCopy; |
| } |
| |
| void WebViewImpl::draggingSessionEnded(NSDraggingSession *, NSPoint endPoint, NSDragOperation operation) |
| { |
| sendDragEndToPage(NSPointToCGPoint(endPoint), operation); |
| } |
| |
| #endif // ENABLE(DRAG_SUPPORT) |
| |
| void WebViewImpl::startWindowDrag() |
| { |
| [[m_view window] performWindowDragWithEvent:m_lastMouseDownEvent.get()]; |
| } |
| |
| void WebViewImpl::startDrag(const WebCore::DragItem& item, const ShareableBitmap::Handle& dragImageHandle) |
| { |
| auto dragImageAsBitmap = ShareableBitmap::create(dragImageHandle); |
| if (!dragImageAsBitmap) { |
| m_page->dragCancelled(); |
| return; |
| } |
| |
| auto dragCGImage = dragImageAsBitmap->makeCGImage(); |
| auto dragNSImage = adoptNS([[NSImage alloc] initWithCGImage:dragCGImage.get() size:dragImageAsBitmap->size()]); |
| |
| WebCore::IntSize size([dragNSImage size]); |
| size.scale(1.0 / m_page->deviceScaleFactor()); |
| [dragNSImage setSize:size]; |
| |
| // The call below could release the view. |
| auto protector = m_view.get(); |
| auto clientDragLocation = item.dragLocationInWindowCoordinates; |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard]; |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| |
| if (auto& info = item.promisedAttachmentInfo) { |
| NSString *utiType = info.contentType; |
| NSString *fileName = info.fileName; |
| if (auto attachment = m_page->attachmentForIdentifier(info.attachmentIdentifier)) { |
| utiType = attachment->utiType(); |
| fileName = attachment->fileName(); |
| } |
| |
| if (!utiType.length) { |
| m_page->dragCancelled(); |
| return; |
| } |
| |
| auto provider = adoptNS([[NSFilePromiseProvider alloc] initWithFileType:utiType delegate:(id <NSFilePromiseProviderDelegate>)m_view.getAutoreleased()]); |
| auto context = adoptNS([[WKPromisedAttachmentContext alloc] initWithIdentifier:info.attachmentIdentifier blobURL:info.blobURL fileName:fileName]); |
| [provider setUserInfo:context.get()]; |
| auto draggingItem = adoptNS([[NSDraggingItem alloc] initWithPasteboardWriter:provider.get()]); |
| [draggingItem setDraggingFrame:NSMakeRect(clientDragLocation.x(), clientDragLocation.y() - size.height(), size.width(), size.height()) contents:dragNSImage.get()]; |
| [m_view beginDraggingSessionWithItems:@[draggingItem.get()] event:m_lastMouseDownEvent.get() source:(id <NSDraggingSource>)m_view.getAutoreleased()]; |
| |
| ASSERT(info.additionalTypes.size() == info.additionalData.size()); |
| if (info.additionalTypes.size() == info.additionalData.size()) { |
| for (size_t index = 0; index < info.additionalTypes.size(); ++index) { |
| auto nsData = info.additionalData[index]->createNSData(); |
| [pasteboard setData:nsData.get() forType:info.additionalTypes[index]]; |
| } |
| } |
| m_page->didStartDrag(); |
| return; |
| } |
| |
| [pasteboard setString:@"" forType:PasteboardTypes::WebDummyPboardType]; |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| [m_view dragImage:dragNSImage.get() at:NSPointFromCGPoint(clientDragLocation) offset:NSZeroSize event:m_lastMouseDownEvent.get() pasteboard:pasteboard source:m_view.getAutoreleased() slideBack:YES]; |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| m_page->didStartDrag(); |
| } |
| |
| static bool matchesExtensionOrEquivalent(const String& filename, const String& extension) |
| { |
| return filename.endsWithIgnoringASCIICase("." + extension) |
| || (equalLettersIgnoringASCIICase(extension, "jpeg") && filename.endsWithIgnoringASCIICase(".jpg")); |
| } |
| |
| void WebViewImpl::setFileAndURLTypes(NSString *filename, NSString *extension, NSString *title, NSString *url, NSString *visibleURL, NSPasteboard *pasteboard) |
| { |
| if (!matchesExtensionOrEquivalent(filename, extension)) |
| filename = [[filename stringByAppendingString:@"."] stringByAppendingString:extension]; |
| |
| [pasteboard setString:visibleURL forType:WebCore::legacyStringPasteboardType()]; |
| [pasteboard setString:visibleURL forType:PasteboardTypes::WebURLPboardType]; |
| [pasteboard setString:title forType:PasteboardTypes::WebURLNamePboardType]; |
| [pasteboard setPropertyList:@[@[visibleURL], @[title]] forType:PasteboardTypes::WebURLsWithTitlesPboardType]; |
| [pasteboard setPropertyList:@[extension] forType:WebCore::legacyFilesPromisePasteboardType()]; |
| m_promisedFilename = filename; |
| m_promisedURL = url; |
| } |
| |
| void WebViewImpl::setPromisedDataForImage(WebCore::Image* image, NSString *filename, NSString *extension, NSString *title, NSString *url, NSString *visibleURL, WebCore::SharedBuffer* archiveBuffer, NSString *pasteboardName) |
| { |
| NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:pasteboardName]; |
| RetainPtr<NSMutableArray> types = adoptNS([[NSMutableArray alloc] initWithObjects:WebCore::legacyFilesPromisePasteboardType(), nil]); |
| |
| [types addObjectsFromArray:archiveBuffer ? PasteboardTypes::forImagesWithArchive() : PasteboardTypes::forImages()]; |
| [pasteboard declareTypes:types.get() owner:m_view.getAutoreleased()]; |
| setFileAndURLTypes(filename, extension, title, url, visibleURL, pasteboard); |
| |
| if (archiveBuffer) |
| [pasteboard setData:archiveBuffer->createNSData().get() forType:PasteboardTypes::WebArchivePboardType]; |
| |
| m_promisedImage = image; |
| } |
| |
| void WebViewImpl::clearPromisedDragImage() |
| { |
| m_promisedImage = nullptr; |
| } |
| |
| void WebViewImpl::pasteboardChangedOwner(NSPasteboard *pasteboard) |
| { |
| clearPromisedDragImage(); |
| m_promisedFilename = emptyString(); |
| m_promisedURL = emptyString(); |
| } |
| |
| void WebViewImpl::provideDataForPasteboard(NSPasteboard *pasteboard, NSString *type) |
| { |
| // FIXME: Need to support NSRTFDPboardType. |
| if ([type isEqual:WebCore::legacyTIFFPasteboardType()] && m_promisedImage) |
| [pasteboard setData:(__bridge NSData *)m_promisedImage->tiffRepresentation() forType:WebCore::legacyTIFFPasteboardType()]; |
| } |
| |
| static BOOL fileExists(NSString *path) |
| { |
| struct stat statBuffer; |
| return !lstat([path fileSystemRepresentation], &statBuffer); |
| } |
| |
| static NSString *pathWithUniqueFilenameForPath(NSString *path) |
| { |
| // "Fix" the filename of the path. |
| NSString *filename = filenameByFixingIllegalCharacters([path lastPathComponent]); |
| path = [[path stringByDeletingLastPathComponent] stringByAppendingPathComponent:filename]; |
| |
| if (fileExists(path)) { |
| // Don't overwrite existing file by appending "-n", "-n.ext" or "-n.ext.ext" to the filename. |
| NSString *extensions = nil; |
| NSString *pathWithoutExtensions; |
| NSString *lastPathComponent = [path lastPathComponent]; |
| NSRange periodRange = [lastPathComponent rangeOfString:@"."]; |
| |
| if (periodRange.location == NSNotFound) { |
| pathWithoutExtensions = path; |
| } else { |
| extensions = [lastPathComponent substringFromIndex:periodRange.location + 1]; |
| lastPathComponent = [lastPathComponent substringToIndex:periodRange.location]; |
| pathWithoutExtensions = [[path stringByDeletingLastPathComponent] stringByAppendingPathComponent:lastPathComponent]; |
| } |
| |
| for (unsigned i = 1; ; i++) { |
| NSString *pathWithAppendedNumber = [NSString stringWithFormat:@"%@-%d", pathWithoutExtensions, i]; |
| path = [extensions length] ? [pathWithAppendedNumber stringByAppendingPathExtension:extensions] : pathWithAppendedNumber; |
| if (!fileExists(path)) |
| break; |
| } |
| } |
| |
| return path; |
| } |
| |
| NSArray *WebViewImpl::namesOfPromisedFilesDroppedAtDestination(NSURL *dropDestination) |
| { |
| RetainPtr<NSFileWrapper> wrapper; |
| RetainPtr<NSData> data; |
| |
| if (m_promisedImage) { |
| data = m_promisedImage->data()->createNSData(); |
| wrapper = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:data.get()]); |
| } else |
| wrapper = adoptNS([[NSFileWrapper alloc] initWithURL:[NSURL URLWithString:m_promisedURL] options:NSFileWrapperReadingImmediate error:nil]); |
| |
| if (wrapper) |
| [wrapper setPreferredFilename:m_promisedFilename]; |
| else { |
| LOG_ERROR("Failed to create image file."); |
| return nil; |
| } |
| |
| // FIXME: Report an error if we fail to create a file. |
| NSString *path = [[dropDestination path] stringByAppendingPathComponent:[wrapper preferredFilename]]; |
| path = pathWithUniqueFilenameForPath(path); |
| if (![wrapper writeToURL:[NSURL fileURLWithPath:path isDirectory:NO] options:NSFileWrapperWritingWithNameUpdating originalContentsURL:nil error:nullptr]) |
| LOG_ERROR("Failed to create image file via -[NSFileWrapper writeToURL:options:originalContentsURL:error:]"); |
| |
| if (!m_promisedURL.isEmpty()) |
| FileSystem::setMetadataURL(String(path), m_promisedURL); |
| |
| return [NSArray arrayWithObject:[path lastPathComponent]]; |
| } |
| |
| void WebViewImpl::requestDOMPasteAccess(const WebCore::IntRect&, const String& originIdentifier, CompletionHandler<void(WebCore::DOMPasteAccessResponse)>&& completion) |
| { |
| ASSERT(!m_domPasteRequestHandler); |
| handleDOMPasteRequestWithResult(WebCore::DOMPasteAccessResponse::DeniedForGesture); |
| |
| NSData *data = [NSPasteboard.generalPasteboard dataForType:@(WebCore::PasteboardCustomData::cocoaType())]; |
| auto buffer = WebCore::SharedBuffer::create(data); |
| if (WebCore::PasteboardCustomData::fromSharedBuffer(buffer.get()).origin == originIdentifier) { |
| completion(WebCore::DOMPasteAccessResponse::GrantedForGesture); |
| return; |
| } |
| |
| m_domPasteMenuDelegate = adoptNS([[WKDOMPasteMenuDelegate alloc] initWithWebViewImpl:*this]); |
| m_domPasteRequestHandler = WTFMove(completion); |
| m_domPasteMenu = adoptNS([[NSMenu alloc] initWithTitle:WebCore::contextMenuItemTagPaste()]); |
| |
| [m_domPasteMenu setDelegate:m_domPasteMenuDelegate.get()]; |
| [m_domPasteMenu setAllowsContextMenuPlugIns:NO]; |
| [m_domPasteMenu insertItemWithTitle:WebCore::contextMenuItemTagPaste() action:@selector(_web_grantDOMPasteAccess) keyEquivalent:emptyString() atIndex:0]; |
| [NSMenu popUpContextMenu:m_domPasteMenu.get() withEvent:m_lastMouseDownEvent.get() forView:m_view.getAutoreleased()]; |
| } |
| |
| void WebViewImpl::handleDOMPasteRequestWithResult(WebCore::DOMPasteAccessResponse response) |
| { |
| if (auto handler = std::exchange(m_domPasteRequestHandler, { })) |
| handler(response); |
| [m_domPasteMenu removeAllItems]; |
| [m_domPasteMenu update]; |
| [m_domPasteMenu cancelTracking]; |
| m_domPasteMenu = nil; |
| m_domPasteMenuDelegate = nil; |
| } |
| |
| 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 checked_cf_cast<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; |
| |
| auto snapshot = ViewSnapshot::create(WTFMove(surface)); |
| snapshot->setVolatile(true); |
| |
| return WTFMove(snapshot); |
| } |
| |
| void WebViewImpl::saveBackForwardSnapshotForCurrentItem() |
| { |
| if (WebBackForwardListItem* item = m_page->backForwardList().currentItem()) |
| m_page->recordNavigationSnapshot(*item); |
| } |
| |
| void WebViewImpl::saveBackForwardSnapshotForItem(WebBackForwardListItem& item) |
| { |
| m_page->recordNavigationSnapshot(item); |
| } |
| |
| ViewGestureController& WebViewImpl::ensureGestureController() |
| { |
| if (!m_gestureController) |
| m_gestureController = makeUnique<ViewGestureController>(m_page); |
| return *m_gestureController; |
| } |
| |
| 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(BlockPtr<void (CGRect)>&& callback) |
| { |
| if (!m_allowsBackForwardNavigationGestures) |
| return; |
| |
| ensureGestureController().setDidMoveSwipeSnapshotCallback(WTFMove(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.getAutoreleased()); |
| m_page->handleWheelEvent(webEvent); |
| } |
| |
| void WebViewImpl::swipeWithEvent(NSEvent *event) |
| { |
| if (m_ignoresNonWheelEvents) |
| return; |
| |
| if (!m_allowsBackForwardNavigationGestures) { |
| [m_view _web_superSwipeWithEvent:event]; |
| return; |
| } |
| |
| if (event.deltaX > 0.0) |
| m_page->goBack(); |
| else if (event.deltaX < 0.0) |
| m_page->goForward(); |
| else |
| [m_view _web_superSwipeWithEvent:event]; |
| } |
| |
| void WebViewImpl::magnifyWithEvent(NSEvent *event) |
| { |
| if (!m_allowsMagnification) { |
| #if ENABLE(MAC_GESTURE_EVENTS) |
| NativeWebGestureEvent webEvent = NativeWebGestureEvent(event, m_view.getAutoreleased()); |
| m_page->handleGestureEvent(webEvent); |
| #endif |
| [m_view _web_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.getAutoreleased()); |
| 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 _web_superSmartMagnifyWithEvent:event]; |
| return; |
| } |
| |
| dismissContentRelativeChildWindowsWithAnimation(false); |
| |
| ensureGestureController().handleSmartMagnificationGesture([m_view convertPoint:event.locationInWindow fromView:nil]); |
| } |
| |
| void WebViewImpl::setLastMouseDownEvent(NSEvent *event) |
| { |
| ASSERT(!event || event.type == NSEventTypeLeftMouseDown || event.type == NSEventTypeRightMouseDown || event.type == NSEventTypeOtherMouseDown); |
| |
| if (event == m_lastMouseDownEvent.get()) |
| return; |
| |
| m_lastMouseDownEvent = event; |
| } |
| |
| #if ENABLE(MAC_GESTURE_EVENTS) |
| void WebViewImpl::rotateWithEvent(NSEvent *event) |
| { |
| NativeWebGestureEvent webEvent = NativeWebGestureEvent(event, m_view.getAutoreleased()); |
| m_page->handleGestureEvent(webEvent); |
| } |
| #endif |
| |
| void WebViewImpl::gestureEventWasNotHandledByWebCore(NSEvent *event) |
| { |
| [m_view _web_gestureEventWasNotHandledByWebCore:event]; |
| } |
| |
| void WebViewImpl::gestureEventWasNotHandledByWebCoreFromViewOnly(NSEvent *event) |
| { |
| #if ENABLE(MAC_GESTURE_EVENTS) |
| if (m_allowsMagnification && m_gestureController) |
| m_gestureController->gestureEventWasNotHandledByWebCore(event, [m_view convertPoint:event.locationInWindow fromView:nil]); |
| #endif |
| } |
| |
| void WebViewImpl::didRestoreScrollPosition() |
| { |
| if (m_gestureController) |
| m_gestureController->didRestoreScrollPosition(); |
| } |
| |
| void WebViewImpl::doneWithKeyEvent(NSEvent *event, bool eventWasHandled) |
| { |
| ASSERT(hasProcessPrivilege(ProcessPrivilege::CanCommunicateWithWindowServer)); |
| if ([event type] != NSEventTypeKeyDown) |
| return; |
| |
| if (tryPostProcessPluginComplexTextInputKeyDown(event)) |
| return; |
| |
| if (eventWasHandled) { |
| [NSCursor setHiddenUntilMouseMoves:YES]; |
| return; |
| } |
| |
| // resending the event may destroy this WKView |
| auto protector = m_view.get(); |
| |
| ASSERT(!m_keyDownEventBeingResent); |
| m_keyDownEventBeingResent = event; |
| [NSApp _setCurrentEvent:event]; |
| [NSApp sendEvent:event]; |
| |
| m_keyDownEventBeingResent = nullptr; |
| } |
| |
| NSArray *WebViewImpl::validAttributesForMarkedText() |
| { |
| static NSArray *validAttributes; |
| if (!validAttributes) { |
| validAttributes = @[ NSUnderlineStyleAttributeName, NSUnderlineColorAttributeName, NSMarkedClauseSegmentAttributeName, |
| #if USE(DICTATION_ALTERNATIVES) |
| NSTextAlternativesAttributeName, |
| #endif |
| #if USE(INSERTION_UNDO_GROUPING) |
| NSTextInsertionUndoableAttributeName, |
| #endif |
| ]; |
| // NSText also supports the following attributes, but it's |
| // hard to tell which are really required for text input to |
| // work well; I have not seen any input method make use of them yet. |
| // NSFontAttributeName, NSForegroundColorAttributeName, |
| // NSBackgroundColorAttributeName, NSLanguageAttributeName. |
| CFRetain(validAttributes); |
| } |
| LOG(TextInput, "validAttributesForMarkedText -> (...)"); |
| return validAttributes; |
| } |
| |
| static Vector<WebCore::CompositionUnderline> extractUnderlines(NSAttributedString *string) |
| { |
| Vector<WebCore::CompositionUnderline> result; |
| int length = string.string.length; |
| |
| for (int i = 0; i < length;) { |
| NSRange range; |
| NSDictionary *attrs = [string attributesAtIndex:i longestEffectiveRange:&range inRange:NSMakeRange(i, length - i)]; |
| |
| if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) { |
| WebCore::Color color = WebCore::Color::black; |
| WebCore::CompositionUnderlineColor compositionUnderlineColor = WebCore::CompositionUnderlineColor::TextColor; |
| if (NSColor *colorAttr = [attrs objectForKey:NSUnderlineColorAttributeName]) { |
| color = WebCore::colorFromNSColor(colorAttr); |
| compositionUnderlineColor = WebCore::CompositionUnderlineColor::GivenColor; |
| } |
| result.append(WebCore::CompositionUnderline(range.location, NSMaxRange(range), compositionUnderlineColor, color, style.intValue > 1)); |
| } |
| |
| i = range.location + range.length; |
| } |
| |
| return result; |
| } |
| |
| static bool eventKeyCodeIsZeroOrNumLockOrFn(NSEvent *event) |
| { |
| unsigned short keyCode = [event keyCode]; |
| return !keyCode || keyCode == 10 || keyCode == 63; |
| } |
| |
| Vector<WebCore::KeypressCommand> WebViewImpl::collectKeyboardLayoutCommandsForEvent(NSEvent *event) |
| { |
| Vector<WebCore::KeypressCommand> commands; |
| |
| if ([event type] != NSEventTypeKeyDown) |
| return commands; |
| |
| ASSERT(!m_collectedKeypressCommands); |
| m_collectedKeypressCommands = &commands; |
| |
| if (NSTextInputContext *context = inputContext()) |
| [context handleEventByKeyboardLayout:event]; |
| else |
| [m_view interpretKeyEvents:[NSArray arrayWithObject:event]]; |
| |
| m_collectedKeypressCommands = nullptr; |
| |
| return commands; |
| } |
| |
| void WebViewImpl::interpretKeyEvent(NSEvent *event, void(^completionHandler)(BOOL handled, const Vector<WebCore::KeypressCommand>& commands)) |
| { |
| // For regular Web content, input methods run before passing a keydown to DOM, but plug-ins get an opportunity to handle the event first. |
| // There is no need to collect commands, as the plug-in cannot execute them. |
| if (pluginComplexTextInputIdentifier()) { |
| completionHandler(NO, { }); |
| return; |
| } |
| |
| if (!inputContext()) { |
| auto commands = collectKeyboardLayoutCommandsForEvent(event); |
| completionHandler(NO, commands); |
| return; |
| } |
| |
| LOG(TextInput, "-> handleEventByInputMethod:%p %@", event, event); |
| [inputContext() handleEventByInputMethod:event completionHandler:[weakThis = makeWeakPtr(*this), capturedEvent = retainPtr(event), capturedBlock = makeBlockPtr(completionHandler)](BOOL handled) { |
| if (!weakThis) { |
| capturedBlock(NO, { }); |
| return; |
| } |
| |
| LOG(TextInput, "... handleEventByInputMethod%s handled", handled ? "" : " not"); |
| if (handled) { |
| capturedBlock(YES, { }); |
| return; |
| } |
| |
| auto commands = weakThis->collectKeyboardLayoutCommandsForEvent(capturedEvent.get()); |
| capturedBlock(NO, commands); |
| }]; |
| } |
| |
| void WebViewImpl::doCommandBySelector(SEL selector) |
| { |
| LOG(TextInput, "doCommandBySelector:\"%s\"", sel_getName(selector)); |
| |
| if (auto* keypressCommands = m_collectedKeypressCommands) { |
| WebCore::KeypressCommand command(NSStringFromSelector(selector)); |
| keypressCommands->append(command); |
| LOG(TextInput, "...stored"); |
| m_page->registerKeypressCommandName(command.commandName); |
| } else { |
| // FIXME: Send the command to Editor synchronously and only send it along the |
| // responder chain if it's a selector that does not correspond to an editing command. |
| [m_view _web_superDoCommandBySelector:selector]; |
| } |
| } |
| |
| void WebViewImpl::insertText(id string) |
| { |
| // Unlike an NSTextInputClient variant with replacementRange, this NSResponder method is called when there is no input context, |
| // so text input processing isn't performed. We are not going to actually insert any text in that case, but saving an insertText |
| // command ensures that a keypress event is dispatched as appropriate. |
| insertText(string, NSMakeRange(NSNotFound, 0)); |
| } |
| |
| void WebViewImpl::insertText(id string, NSRange replacementRange) |
| { |
| BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; |
| ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]); |
| |
| if (replacementRange.location != NSNotFound) |
| LOG(TextInput, "insertText:\"%@\" replacementRange:(%u, %u)", isAttributedString ? [string string] : string, replacementRange.location, replacementRange.length); |
| else |
| LOG(TextInput, "insertText:\"%@\"", isAttributedString ? [string string] : string); |
| |
| NSString *text; |
| Vector<WebCore::TextAlternativeWithRange> dictationAlternatives; |
| |
| bool registerUndoGroup = false; |
| if (isAttributedString) { |
| #if USE(DICTATION_ALTERNATIVES) |
| WebCore::collectDictationTextAlternatives(string, dictationAlternatives); |
| #endif |
| #if USE(INSERTION_UNDO_GROUPING) |
| registerUndoGroup = WebCore::shouldRegisterInsertionUndoGroup(string); |
| #endif |
| // FIXME: We ignore most attributes from the string, so for example inserting from Character Palette loses font and glyph variation data. |
| text = [string string]; |
| } else |
| text = string; |
| |
| m_isTextInsertionReplacingSoftSpace = false; |
| if (m_softSpaceRange.location != NSNotFound && (replacementRange.location == NSMaxRange(m_softSpaceRange) || replacementRange.location == NSNotFound) && replacementRange.length == 0 && [[NSSpellChecker sharedSpellChecker] deletesAutospaceBeforeString:text language:nil]) { |
| replacementRange = m_softSpaceRange; |
| m_isTextInsertionReplacingSoftSpace = true; |
| } |
| m_softSpaceRange = NSMakeRange(NSNotFound, 0); |
| |
| // insertText can be called for several reasons: |
| // - If it's from normal key event processing (including key bindings), we save the action to perform it later. |
| // - If it's from an input method, then we should insert the text now. |
| // - If it's sent outside of keyboard event processing (e.g. from Character Viewer, or when confirming an inline input area with a mouse), |
| // then we also execute it immediately, as there will be no other chance. |
| Vector<WebCore::KeypressCommand>* keypressCommands = m_collectedKeypressCommands; |
| if (keypressCommands && !m_isTextInsertionReplacingSoftSpace) { |
| ASSERT(replacementRange.location == NSNotFound); |
| WebCore::KeypressCommand command("insertText:", text); |
| keypressCommands->append(command); |
| LOG(TextInput, "...stored"); |
| m_page->registerKeypressCommandName(command.commandName); |
| return; |
| } |
| |
| String eventText = text; |
| eventText.replace(NSBackTabCharacter, NSTabCharacter); // same thing is done in KeyEventMac.mm in WebCore |
| if (!dictationAlternatives.isEmpty()) |
| m_page->insertDictatedTextAsync(eventText, replacementRange, dictationAlternatives, registerUndoGroup); |
| else { |
| InsertTextOptions options; |
| options.registerUndoGroup = registerUndoGroup; |
| options.editingRangeIsRelativeTo = m_isTextInsertionReplacingSoftSpace ? EditingRangeIsRelativeTo::Paragraph : EditingRangeIsRelativeTo::EditableRoot; |
| options.suppressSelectionUpdate = m_isTextInsertionReplacingSoftSpace; |
| |
| m_page->insertTextAsync(eventText, replacementRange, WTFMove(options)); |
| } |
| } |
| |
| void WebViewImpl::selectedRangeWithCompletionHandler(void(^completionHandlerPtr)(NSRange selectedRange)) |
| { |
| auto completionHandler = adoptNS([completionHandlerPtr copy]); |
| |
| LOG(TextInput, "selectedRange"); |
| m_page->getSelectedRangeAsync([completionHandler](const EditingRange& editingRangeResult, WebKit::CallbackBase::Error error) { |
| void (^completionHandlerBlock)(NSRange) = (void (^)(NSRange))completionHandler.get(); |
| if (error != WebKit::CallbackBase::Error::None) { |
| LOG(TextInput, " ...selectedRange failed."); |
| completionHandlerBlock(NSMakeRange(NSNotFound, 0)); |
| return; |
| } |
| NSRange result = editingRangeResult; |
| if (result.location == NSNotFound) |
| LOG(TextInput, " -> selectedRange returned (NSNotFound, %llu)", result.length); |
| else |
| LOG(TextInput, " -> selectedRange returned (%llu, %llu)", result.location, result.length); |
| completionHandlerBlock(result); |
| }); |
| } |
| |
| void WebViewImpl::markedRangeWithCompletionHandler(void(^completionHandlerPtr)(NSRange markedRange)) |
| { |
| auto completionHandler = adoptNS([completionHandlerPtr copy]); |
| |
| LOG(TextInput, "markedRange"); |
| m_page->getMarkedRangeAsync([completionHandler](const EditingRange& editingRangeResult, WebKit::CallbackBase::Error error) { |
| void (^completionHandlerBlock)(NSRange) = (void (^)(NSRange))completionHandler.get(); |
| if (error != WebKit::CallbackBase::Error::None) { |
| LOG(TextInput, " ...markedRange failed."); |
| completionHandlerBlock(NSMakeRange(NSNotFound, 0)); |
| return; |
| } |
| NSRange result = editingRangeResult; |
| if (result.location == NSNotFound) |
| LOG(TextInput, " -> markedRange returned (NSNotFound, %llu)", result.length); |
| else |
| LOG(TextInput, " -> markedRange returned (%llu, %llu)", result.location, result.length); |
| completionHandlerBlock(result); |
| }); |
| } |
| |
| void WebViewImpl::hasMarkedTextWithCompletionHandler(void(^completionHandler)(BOOL hasMarkedText)) |
| { |
| LOG(TextInput, "hasMarkedText"); |
| m_page->hasMarkedText([completionHandler = makeBlockPtr(completionHandler)] (bool result) { |
| completionHandler(result); |
| LOG(TextInput, " -> hasMarkedText returned %u", result); |
| }); |
| } |
| |
| void WebViewImpl::attributedSubstringForProposedRange(NSRange proposedRange, void(^completionHandlerPtr)(NSAttributedString *attrString, NSRange actualRange)) |
| { |
| auto completionHandler = adoptNS([completionHandlerPtr copy]); |
| |
| LOG(TextInput, "attributedSubstringFromRange:(%u, %u)", proposedRange.location, proposedRange.length); |
| m_page->attributedSubstringForCharacterRangeAsync(proposedRange, [completionHandler](const AttributedString& string, const EditingRange& actualRange, WebKit::CallbackBase::Error error) { |
| void (^completionHandlerBlock)(NSAttributedString *, NSRange) = (void (^)(NSAttributedString *, NSRange))completionHandler.get(); |
| if (error != WebKit::CallbackBase::Error::None) { |
| LOG(TextInput, " ...attributedSubstringFromRange failed."); |
| completionHandlerBlock(0, NSMakeRange(NSNotFound, 0)); |
| return; |
| } |
| NSAttributedString *attributedString = string; |
| LOG(TextInput, " -> attributedSubstringFromRange returned %@", [attributedString string]); |
| completionHandlerBlock([[attributedString retain] autorelease], actualRange); |
| }); |
| } |
| |
| void WebViewImpl::firstRectForCharacterRange(NSRange range, void(^completionHandlerPtr)(NSRect firstRect, NSRange actualRange)) |
| { |
| auto completionHandler = adoptNS([completionHandlerPtr copy]); |
| |
| LOG(TextInput, "firstRectForCharacterRange:(%u, %u)", range.location, range.length); |
| |
| // Just to match NSTextView's behavior. Regression tests cannot detect this; |
| // to reproduce, use a test application from http://bugs.webkit.org/show_bug.cgi?id=4682 |
| // (type something; try ranges (1, -1) and (2, -1). |
| if ((range.location + range.length < range.location) && (range.location + range.length != 0)) |
| range.length = 0; |
| |
| if (range.location == NSNotFound) { |
| LOG(TextInput, " -> NSZeroRect"); |
| completionHandlerPtr(NSZeroRect, range); |
| return; |
| } |
| |
| auto weakThis = makeWeakPtr(*this); |
| m_page->firstRectForCharacterRangeAsync(range, [weakThis, completionHandler](const WebCore::IntRect& rect, const EditingRange& actualRange, WebKit::CallbackBase::Error error) { |
| auto completionHandlerBlock = (void (^)(NSRect, NSRange))completionHandler.get(); |
| if (!weakThis) { |
| LOG(TextInput, " ...firstRectForCharacterRange failed (WebViewImpl was destroyed)."); |
| completionHandlerBlock(NSZeroRect, NSMakeRange(NSNotFound, 0)); |
| return; |
| } |
| |
| if (error != WebKit::CallbackBase::Error::None) { |
| LOG(TextInput, " ...firstRectForCharacterRange failed."); |
| completionHandlerBlock(NSZeroRect, NSMakeRange(NSNotFound, 0)); |
| return; |
| } |
| |
| NSRect resultRect = [weakThis->m_view convertRect:rect toView:nil]; |
| resultRect = [[weakThis->m_view window] convertRectToScreen:resultRect]; |
| |
| LOG(TextInput, " -> firstRectForCharacterRange returned (%f, %f, %f, %f)", resultRect.origin.x, resultRect.origin.y, resultRect.size.width, resultRect.size.height); |
| completionHandlerBlock(resultRect, actualRange); |
| }); |
| } |
| |
| void WebViewImpl::characterIndexForPoint(NSPoint point, void(^completionHandlerPtr)(NSUInteger)) |
| { |
| auto completionHandler = adoptNS([completionHandlerPtr copy]); |
| |
| LOG(TextInput, "characterIndexForPoint:(%f, %f)", point.x, point.y); |
| |
| NSWindow *window = [m_view window]; |
| |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| if (window) |
| point = [window convertScreenToBase:point]; |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| point = [m_view convertPoint:point fromView:nil]; // the point is relative to the main frame |
| |
| m_page->characterIndexForPointAsync(WebCore::IntPoint(point), [completionHandler](uint64_t result, WebKit::CallbackBase::Error error) { |
| void (^completionHandlerBlock)(NSUInteger) = (void (^)(NSUInteger))completionHandler.get(); |
| if (error != WebKit::CallbackBase::Error::None) { |
| LOG(TextInput, " ...characterIndexForPoint failed."); |
| completionHandlerBlock(0); |
| return; |
| } |
| if (result == notFound) |
| result = NSNotFound; |
| LOG(TextInput, " -> characterIndexForPoint returned %lu", result); |
| completionHandlerBlock(result); |
| }); |
| } |
| |
| NSTextInputContext *WebViewImpl::inputContext() |
| { |
| if (pluginComplexTextInputIdentifier()) { |
| ASSERT(!m_collectedKeypressCommands); // Should not get here from -_interpretKeyEvent:completionHandler:, we only use WKTextInputWindowController after giving the plug-in a chance to handle keydown natively. |
| return [[WKTextInputWindowController sharedTextInputWindowController] inputContext]; |
| } |
| |
| // Disable text input machinery when in non-editable content. An invisible inline input area affects performance, and can prevent Expose from working. |
| if (!m_page->editorState().isContentEditable) |
| return nil; |
| |
| return [m_view _web_superInputContext]; |
| } |
| |
| void WebViewImpl::unmarkText() |
| { |
| LOG(TextInput, "unmarkText"); |
| |
| m_page->confirmCompositionAsync(); |
| } |
| |
| void WebViewImpl::setMarkedText(id string, NSRange selectedRange, NSRange replacementRange) |
| { |
| BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; |
| ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]); |
| |
| LOG(TextInput, "setMarkedText:\"%@\" selectedRange:(%u, %u) replacementRange:(%u, %u)", isAttributedString ? [string string] : string, selectedRange.location, selectedRange.length, replacementRange.location, replacementRange.length); |
| |
| Vector<WebCore::CompositionUnderline> underlines; |
| NSString *text; |
| |
| if (isAttributedString) { |
| // FIXME: We ignore most attributes from the string, so an input method cannot specify e.g. a font or a glyph variation. |
| text = [string string]; |
| underlines = extractUnderlines(string); |
| } else { |
| text = string; |
| underlines.append(WebCore::CompositionUnderline(0, [text length], WebCore::CompositionUnderlineColor::TextColor, WebCore::Color::black, false)); |
| } |
| |
| if (inSecureInputState()) { |
| // In password fields, we only allow ASCII dead keys, and don't allow inline input, matching NSSecureTextInputField. |
| // Allowing ASCII dead keys is necessary to enable full Roman input when using a Vietnamese keyboard. |
| ASSERT(!m_page->editorState().hasComposition); |
| notifyInputContextAboutDiscardedComposition(); |
| // FIXME: We should store the command to handle it after DOM event processing, as it's regular keyboard input now, not a composition. |
| if ([text length] == 1 && isASCII([text characterAtIndex:0])) |
| m_page->insertTextAsync(text, replacementRange, { }); |
| else |
| NSBeep(); |
| return; |
| } |
| |
| m_page->setCompositionAsync(text, underlines, selectedRange, replacementRange); |
| } |
| |
| // Synchronous NSTextInputClient is still implemented to catch spurious sync calls. Remove when that is no longer needed. |
| |
| NSRange WebViewImpl::selectedRange() |
| { |
| ASSERT_NOT_REACHED(); |
| return NSMakeRange(NSNotFound, 0); |
| } |
| |
| bool WebViewImpl::hasMarkedText() |
| { |
| ASSERT_NOT_REACHED(); |
| return NO; |
| } |
| |
| NSRange WebViewImpl::markedRange() |
| { |
| ASSERT_NOT_REACHED(); |
| return NSMakeRange(NSNotFound, 0); |
| } |
| |
| NSAttributedString *WebViewImpl::attributedSubstringForProposedRange(NSRange nsRange, NSRangePointer actualRange) |
| { |
| ASSERT_NOT_REACHED(); |
| return nil; |
| } |
| |
| NSUInteger WebViewImpl::characterIndexForPoint(NSPoint point) |
| { |
| ASSERT_NOT_REACHED(); |
| return 0; |
| } |
| |
| NSRect WebViewImpl::firstRectForCharacterRange(NSRange range, NSRangePointer actualRange) |
| { |
| ASSERT_NOT_REACHED(); |
| return NSZeroRect; |
| } |
| |
| bool WebViewImpl::performKeyEquivalent(NSEvent *event) |
| { |
| if (ignoresNonWheelEvents()) |
| return NO; |
| |
| // There's a chance that responding to this event will run a nested event loop, and |
| // fetching a new event might release the old one. Retaining and then autoreleasing |
| // the current event prevents that from causing a problem inside WebKit or AppKit code. |
| CFRetain((__bridge CFTypeRef)event); |
| CFAutorelease((__bridge CFTypeRef)event); |
| |
| // We get Esc key here after processing either Esc or Cmd+period. The former starts as a keyDown, and the latter starts as a key equivalent, |
| // but both get transformed to a cancelOperation: command, executing which passes an Esc key event to -performKeyEquivalent:. |
| // Don't interpret this event again, avoiding re-entrancy and infinite loops. |
| if ([[event charactersIgnoringModifiers] isEqualToString:@"\e"] && !([event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask)) |
| return [m_view _web_superPerformKeyEquivalent:event]; |
| |
| if (m_keyDownEventBeingResent) { |
| // WebCore has already seen the event, no need for custom processing. |
| // Note that we can get multiple events for each event being re-sent. For example, for Cmd+'=' AppKit |
| // first performs the original key equivalent, and if that isn't handled, it dispatches a synthetic Cmd+'+'. |
| return [m_view _web_superPerformKeyEquivalent:event]; |
| } |
| |
| disableComplexTextInputIfNecessary(); |
| |
| // Pass key combos through WebCore if there is a key binding available for |
| // this event. This lets webpages have a crack at intercepting key-modified keypresses. |
| // FIXME: Why is the firstResponder check needed? |
| if (m_view.getAutoreleased() == [m_view window].firstResponder) { |
| interpretKeyEvent(event, [weakThis = makeWeakPtr(*this), capturedEvent = retainPtr(event)](BOOL handledByInputMethod, const Vector<WebCore::KeypressCommand>& commands) { |
| if (weakThis) |
| weakThis->m_page->handleKeyboardEvent(NativeWebKeyboardEvent(capturedEvent.get(), handledByInputMethod, false, commands)); |
| }); |
| return YES; |
| } |
| |
| return [m_view _web_superPerformKeyEquivalent:event]; |
| } |
| |
| void WebViewImpl::keyUp(NSEvent *event) |
| { |
| if (ignoresNonWheelEvents()) |
| return; |
| |
| LOG(TextInput, "keyUp:%p %@", event, event); |
| |
| m_isTextInsertionReplacingSoftSpace = false; |
| interpretKeyEvent(event, [weakThis = makeWeakPtr(*this), capturedEvent = retainPtr(event)](BOOL handledByInputMethod, const Vector<WebCore::KeypressCommand>& commands) { |
| ASSERT(!handledByInputMethod || commands.isEmpty()); |
| if (weakThis) |
| weakThis->m_page->handleKeyboardEvent(NativeWebKeyboardEvent(capturedEvent.get(), handledByInputMethod, weakThis->m_isTextInsertionReplacingSoftSpace, commands)); |
| }); |
| } |
| |
| void WebViewImpl::keyDown(NSEvent *event) |
| { |
| if (ignoresNonWheelEvents()) |
| return; |
| |
| LOG(TextInput, "keyDown:%p %@%s", event, event, (event == m_keyDownEventBeingResent) ? " (re-sent)" : ""); |
| |
| if (tryHandlePluginComplexTextInputKeyDown(event)) { |
| LOG(TextInput, "...handled by plug-in"); |
| return; |
| } |
| |
| // We could be receiving a key down from AppKit if we have re-sent an event |
| // that maps to an action that is currently unavailable (for example a copy when |
| // there is no range selection). |
| // If this is the case we should ignore the key down. |
| if (m_keyDownEventBeingResent == event) { |
| [m_view _web_superKeyDown:event]; |
| return; |
| } |
| |
| m_isTextInsertionReplacingSoftSpace = false; |
| interpretKeyEvent(event, [weakThis = makeWeakPtr(*this), capturedEvent = retainPtr(event)](BOOL handledByInputMethod, const Vector<WebCore::KeypressCommand>& commands) { |
| ASSERT(!handledByInputMethod || commands.isEmpty()); |
| if (weakThis) |
| weakThis->m_page->handleKeyboardEvent(NativeWebKeyboardEvent(capturedEvent.get(), handledByInputMethod, weakThis->m_isTextInsertionReplacingSoftSpace, commands)); |
| }); |
| } |
| |
| void WebViewImpl::flagsChanged(NSEvent *event) |
| { |
| if (ignoresNonWheelEvents()) |
| return; |
| |
| LOG(TextInput, "flagsChanged:%p %@", event, event); |
| |
| // Don't make an event from the num lock and function keys |
| if (eventKeyCodeIsZeroOrNumLockOrFn(event)) |
| return; |
| |
| interpretKeyEvent(event, ^(BOOL handledByInputMethod, const Vector<WebCore::KeypressCommand>& commands) { |
| m_page->handleKeyboardEvent(NativeWebKeyboardEvent(event, handledByInputMethod, false, commands)); |
| }); |
| } |
| |
| #define NATIVE_MOUSE_EVENT_HANDLER(EventName) \ |
| void WebViewImpl::EventName(NSEvent *event) \ |
| { \ |
| if (m_ignoresNonWheelEvents) \ |
| return; \ |
| if (NSTextInputContext *context = [m_view inputContext]) { \ |
| auto weakThis = makeWeakPtr(*this); \ |
| RetainPtr<NSEvent> retainedEvent = event; \ |
| [context handleEvent:event completionHandler:[weakThis, retainedEvent] (BOOL handled) { \ |
| if (!weakThis) \ |
| return; \ |
| if (handled) \ |
| LOG(TextInput, "%s was handled by text input context", String(#EventName).substring(0, String(#EventName).find("Internal")).ascii().data()); \ |
| else { \ |
| NativeWebMouseEvent webEvent(retainedEvent.get(), weakThis->m_lastPressureEvent.get(), weakThis->m_view.getAutoreleased()); \ |
| weakThis->m_page->handleMouseEvent(webEvent); \ |
| } \ |
| }]; \ |
| return; \ |
| } \ |
| NativeWebMouseEvent webEvent(event, m_lastPressureEvent.get(), m_view.getAutoreleased()); \ |
| m_page->handleMouseEvent(webEvent); \ |
| } |
| #define NATIVE_MOUSE_EVENT_HANDLER_INTERNAL(EventName) \ |
| void WebViewImpl::EventName(NSEvent *event) \ |
| { \ |
| if (m_ignoresNonWheelEvents || m_safeBrowsingWarning) \ |
| return; \ |
| if (NSTextInputContext *context = [m_view inputContext]) { \ |
| auto weakThis = makeWeakPtr(*this); \ |
| RetainPtr<NSEvent> retainedEvent = event; \ |
| [context handleEvent:event completionHandler:[weakThis, retainedEvent] (BOOL handled) { \ |
| if (!weakThis) \ |
| return; \ |
| if (handled) \ |
| LOG(TextInput, "%s was handled by text input context", String(#EventName).substring(0, String(#EventName).find("Internal")).ascii().data()); \ |
| else { \ |
| NativeWebMouseEvent webEvent(retainedEvent.get(), weakThis->m_lastPressureEvent.get(), weakThis->m_view.getAutoreleased()); \ |
| weakThis->m_page->handleMouseEvent(webEvent); \ |
| } \ |
| }]; \ |
| return; \ |
| } \ |
| NativeWebMouseEvent webEvent(event, m_lastPressureEvent.get(), m_view.getAutoreleased()); \ |
| m_page->handleMouseEvent(webEvent); \ |
| } |
| |
| NATIVE_MOUSE_EVENT_HANDLER(mouseEntered) |
| NATIVE_MOUSE_EVENT_HANDLER(mouseExited) |
| NATIVE_MOUSE_EVENT_HANDLER(otherMouseDown) |
| NATIVE_MOUSE_EVENT_HANDLER(otherMouseDragged) |
| NATIVE_MOUSE_EVENT_HANDLER(otherMouseUp) |
| NATIVE_MOUSE_EVENT_HANDLER(rightMouseDown) |
| NATIVE_MOUSE_EVENT_HANDLER(rightMouseDragged) |
| NATIVE_MOUSE_EVENT_HANDLER(rightMouseUp) |
| |
| NATIVE_MOUSE_EVENT_HANDLER_INTERNAL(mouseMovedInternal) |
| NATIVE_MOUSE_EVENT_HANDLER_INTERNAL(mouseDownInternal) |
| NATIVE_MOUSE_EVENT_HANDLER_INTERNAL(mouseUpInternal) |
| NATIVE_MOUSE_EVENT_HANDLER_INTERNAL(mouseDraggedInternal) |
| |
| #undef NATIVE_MOUSE_EVENT_HANDLER |
| #undef NATIVE_MOUSE_EVENT_HANDLER_INTERNAL |
| |
| void WebViewImpl::mouseMoved(NSEvent *event) |
| { |
| if (m_ignoresNonWheelEvents) |
| return; |
| |
| // When a view is first responder, it gets mouse moved events even when the mouse is outside its visible rect. |
| if (m_view.getAutoreleased() == [m_view window].firstResponder && !NSPointInRect([m_view convertPoint:[event locationInWindow] fromView:nil], [m_view visibleRect])) |
| return; |
| |
| mouseMovedInternal(event); |
| } |
| |
| _WKRectEdge WebViewImpl::pinnedState() |
| { |
| _WKRectEdge state = _WKRectEdgeNone; |
| if (m_page->isPinnedToLeftSide()) |
| state |= _WKRectEdgeLeft; |
| if (m_page->isPinnedToRightSide()) |
| state |= _WKRectEdgeRight; |
| if (m_page->isPinnedToTopSide()) |
| state |= _WKRectEdgeTop; |
| if (m_page->isPinnedToBottomSide()) |
| state |= _WKRectEdgeBottom; |
| return state; |
| } |
| |
| _WKRectEdge WebViewImpl::rubberBandingEnabled() |
| { |
| _WKRectEdge state = _WKRectEdgeNone; |
| if (m_page->rubberBandsAtLeft()) |
| state |= _WKRectEdgeLeft; |
| if (m_page->rubberBandsAtRight()) |
| state |= _WKRectEdgeRight; |
| if (m_page->rubberBandsAtTop()) |
| state |= _WKRectEdgeTop; |
| if (m_page->rubberBandsAtBottom()) |
| state |= _WKRectEdgeBottom; |
| return state; |
| } |
| |
| void WebViewImpl::setRubberBandingEnabled(_WKRectEdge state) |
| { |
| m_page->setRubberBandsAtLeft(state & _WKRectEdgeLeft); |
| m_page->setRubberBandsAtRight(state & _WKRectEdgeRight); |
| m_page->setRubberBandsAtTop(state & _WKRectEdgeTop); |
| m_page->setRubberBandsAtBottom(state & _WKRectEdgeBottom); |
| } |
| |
| void WebViewImpl::mouseDown(NSEvent *event) |
| { |
| if (m_ignoresNonWheelEvents) |
| return; |
| |
| setLastMouseDownEvent(event); |
| setIgnoresMouseDraggedEvents(false); |
| |
| mouseDownInternal(event); |
| } |
| |
| void WebViewImpl::mouseUp(NSEvent *event) |
| { |
| if (m_ignoresNonWheelEvents) |
| return; |
| |
| setLastMouseDownEvent(nil); |
| mouseUpInternal(event); |
| } |
| |
| void WebViewImpl::mouseDragged(NSEvent *event) |
| { |
| if (m_ignoresNonWheelEvents) |
| return; |
| if (ignoresMouseDraggedEvents()) |
| return; |
| |
| mouseDraggedInternal(event); |
| } |
| |
| bool WebViewImpl::windowIsFrontWindowUnderMouse(NSEvent *event) |
| { |
| NSRect eventScreenPosition = [[m_view window] convertRectToScreen:NSMakeRect(event.locationInWindow.x, event.locationInWindow.y, 0, 0)]; |
| NSInteger eventWindowNumber = [NSWindow windowNumberAtPoint:eventScreenPosition.origin belowWindowWithWindowNumber:0]; |
| |
| return [m_view window].windowNumber != eventWindowNumber; |
| } |
| |
| static WebCore::UserInterfaceLayoutDirection toUserInterfaceLayoutDirection(NSUserInterfaceLayoutDirection direction) |
| { |
| switch (direction) { |
| case NSUserInterfaceLayoutDirectionLeftToRight: |
| return WebCore::UserInterfaceLayoutDirection::LTR; |
| case NSUserInterfaceLayoutDirectionRightToLeft: |
| return WebCore::UserInterfaceLayoutDirection::RTL; |
| } |
| |
| ASSERT_NOT_REACHED(); |
| return WebCore::UserInterfaceLayoutDirection::LTR; |
| } |
| |
| WebCore::UserInterfaceLayoutDirection WebViewImpl::userInterfaceLayoutDirection() |
| { |
| return toUserInterfaceLayoutDirection([m_view userInterfaceLayoutDirection]); |
| } |
| |
| void WebViewImpl::setUserInterfaceLayoutDirection(NSUserInterfaceLayoutDirection direction) |
| { |
| m_page->setUserInterfaceLayoutDirection(toUserInterfaceLayoutDirection(direction)); |
| } |
| |
| bool WebViewImpl::beginBackSwipeForTesting() |
| { |
| if (!m_gestureController) |
| return false; |
| return m_gestureController->beginSimulatedSwipeInDirectionForTesting(ViewGestureController::SwipeDirection::Back); |
| } |
| |
| bool WebViewImpl::completeBackSwipeForTesting() |
| { |
| if (!m_gestureController) |
| return false; |
| return m_gestureController->completeSimulatedSwipeInDirectionForTesting(ViewGestureController::SwipeDirection::Back); |
| } |
| |
| void WebViewImpl::setUseSystemAppearance(bool useSystemAppearance) |
| { |
| m_page->setUseSystemAppearance(useSystemAppearance); |
| } |
| |
| bool WebViewImpl::useSystemAppearance() |
| { |
| return m_page->useSystemAppearance(); |
| } |
| |
| void WebViewImpl::effectiveAppearanceDidChange() |
| { |
| m_page->effectiveAppearanceDidChange(); |
| } |
| |
| bool WebViewImpl::effectiveAppearanceIsDark() |
| { |
| #if HAVE(OS_DARK_MODE_SUPPORT) |
| NSAppearanceName appearance = [[m_view effectiveAppearance] bestMatchFromAppearancesWithNames:@[ NSAppearanceNameAqua, NSAppearanceNameDarkAqua ]]; |
| return [appearance isEqualToString:NSAppearanceNameDarkAqua]; |
| #else |
| return false; |
| #endif |
| } |
| |
| bool WebViewImpl::effectiveUserInterfaceLevelIsElevated() |
| { |
| return false; |
| } |
| |
| } // namespace WebKit |
| |
| #endif // PLATFORM(MAC) |