| /* |
| * Copyright (C) 2010, 2011 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 "WKView.h" |
| |
| #if PLATFORM(MAC) |
| |
| #if USE(DICTATION_ALTERNATIVES) |
| #import <AppKit/NSTextAlternatives.h> |
| #import <AppKit/NSAttributedString.h> |
| #endif |
| |
| #import "APILegacyContextHistoryClient.h" |
| #import "APIPageConfiguration.h" |
| #import "AttributedString.h" |
| #import "DataReference.h" |
| #import "EditingRange.h" |
| #import "EditorState.h" |
| #import "LayerTreeContext.h" |
| #import "Logging.h" |
| #import "NativeWebKeyboardEvent.h" |
| #import "NativeWebMouseEvent.h" |
| #import "NativeWebWheelEvent.h" |
| #import "PageClientImpl.h" |
| #import "PasteboardTypes.h" |
| #import "RemoteLayerTreeDrawingAreaProxy.h" |
| #import "RemoteObjectRegistry.h" |
| #import "RemoteObjectRegistryMessages.h" |
| #import "StringUtilities.h" |
| #import "TextChecker.h" |
| #import "TextCheckerState.h" |
| #import "TiledCoreAnimationDrawingAreaProxy.h" |
| #import "WKAPICast.h" |
| #import "WKFullScreenWindowController.h" |
| #import "WKLayoutMode.h" |
| #import "WKPrintingView.h" |
| #import "WKProcessPoolInternal.h" |
| #import "WKStringCF.h" |
| #import "WKTextInputWindowController.h" |
| #import "WKViewInternal.h" |
| #import "WKViewPrivate.h" |
| #import "WKWebView.h" |
| #import "WebBackForwardList.h" |
| #import "WebEventFactory.h" |
| #import "WebHitTestResultData.h" |
| #import "WebInspectorProxy.h" |
| #import "WebKit2Initialize.h" |
| #import "WebPage.h" |
| #import "WebPageGroup.h" |
| #import "WebPageProxy.h" |
| #import "WebPreferences.h" |
| #import "WebProcessPool.h" |
| #import "WebProcessProxy.h" |
| #import "WebSystemInterface.h" |
| #import "WebViewImpl.h" |
| #import "_WKRemoteObjectRegistryInternal.h" |
| #import <QuartzCore/QuartzCore.h> |
| #import <WebCore/AXObjectCache.h> |
| #import <WebCore/ColorMac.h> |
| #import <WebCore/CoreGraphicsSPI.h> |
| #import <WebCore/DataDetectorsSPI.h> |
| #import <WebCore/DictionaryLookup.h> |
| #import <WebCore/DragController.h> |
| #import <WebCore/DragData.h> |
| #import <WebCore/FloatRect.h> |
| #import <WebCore/Image.h> |
| #import <WebCore/IntRect.h> |
| #import <WebCore/FileSystem.h> |
| #import <WebCore/KeyboardEvent.h> |
| #import <WebCore/LocalizedStrings.h> |
| #import <WebCore/NSMenuSPI.h> |
| #import <WebCore/PlatformEventFactoryMac.h> |
| #import <WebCore/PlatformScreen.h> |
| #import <WebCore/Region.h> |
| #import <WebCore/RuntimeApplicationChecks.h> |
| #import <WebCore/SharedBuffer.h> |
| #import <WebCore/TextAlternativeWithRange.h> |
| #import <WebCore/TextIndicator.h> |
| #import <WebCore/TextIndicatorWindow.h> |
| #import <WebCore/TextUndoInsertionMarkupMac.h> |
| #import <WebCore/WebCoreCALayerExtras.h> |
| #import <WebCore/WebCoreFullScreenPlaceholderView.h> |
| #import <WebCore/WebCoreFullScreenWindow.h> |
| #import <WebCore/WebCoreNSStringExtras.h> |
| #import <WebKitSystemInterface.h> |
| #import <sys/stat.h> |
| #import <wtf/RefPtr.h> |
| #import <wtf/RetainPtr.h> |
| #import <wtf/RunLoop.h> |
| |
| /* API internals. */ |
| #import "WKBrowsingContextControllerInternal.h" |
| #import "WKBrowsingContextGroupPrivate.h" |
| #import "WKProcessGroupPrivate.h" |
| |
| @interface NSApplication (WKNSApplicationDetails) |
| - (void)speakString:(NSString *)string; |
| - (void)_setCurrentEvent:(NSEvent *)event; |
| @end |
| |
| #if USE(ASYNC_NSTEXTINPUTCLIENT) |
| @interface NSTextInputContext (WKNSTextInputContextDetails) |
| - (void)handleEvent:(NSEvent *)theEvent completionHandler:(void(^)(BOOL handled))completionHandler; |
| - (void)handleEventByInputMethod:(NSEvent *)theEvent completionHandler:(void(^)(BOOL handled))completionHandler; |
| - (BOOL)handleEventByKeyboardLayout:(NSEvent *)theEvent; |
| @end |
| #endif |
| |
| using namespace WebKit; |
| using namespace WebCore; |
| |
| namespace WebKit { |
| |
| typedef id <NSValidatedUserInterfaceItem> ValidationItem; |
| typedef Vector<RetainPtr<ValidationItem>> ValidationVector; |
| typedef HashMap<String, ValidationVector> ValidationMap; |
| |
| } |
| |
| #if !USE(ASYNC_NSTEXTINPUTCLIENT) |
| struct WKViewInterpretKeyEventsParameters { |
| bool eventInterpretationHadSideEffects; |
| bool consumedByIM; |
| bool executingSavedKeypressCommands; |
| Vector<KeypressCommand>* commands; |
| }; |
| #endif |
| |
| @interface WKViewData : NSObject { |
| @public |
| std::unique_ptr<PageClientImpl> _pageClient; |
| RefPtr<WebPageProxy> _page; |
| std::unique_ptr<WebViewImpl> _impl; |
| |
| #if WK_API_ENABLED |
| RetainPtr<WKBrowsingContextController> _browsingContextController; |
| RetainPtr<NSView> _inspectorAttachmentView; |
| |
| RetainPtr<_WKRemoteObjectRegistry> _remoteObjectRegistry; |
| #endif |
| |
| RetainPtr<NSTrackingArea> _primaryTrackingArea; |
| |
| RetainPtr<id> _remoteAccessibilityChild; |
| |
| // For asynchronous validation. |
| ValidationMap _validationMap; |
| |
| // We keep here the event when resending it to |
| // the application to distinguish the case of a new event from one |
| // that has been already sent to WebCore. |
| RetainPtr<NSEvent> _keyDownEventBeingResent; |
| #if USE(ASYNC_NSTEXTINPUTCLIENT) |
| Vector<KeypressCommand>* _collectedKeypressCommands; |
| #else |
| WKViewInterpretKeyEventsParameters* _interpretKeyEventsParameters; |
| #endif |
| |
| BOOL _willBecomeFirstResponderAgain; |
| NSEvent *_mouseDownEvent; |
| |
| BOOL _hasSpellCheckerDocumentTag; |
| NSInteger _spellCheckerDocumentTag; |
| |
| RefPtr<WebCore::Image> _promisedImage; |
| String _promisedFilename; |
| String _promisedURL; |
| |
| BOOL _windowOcclusionDetectionEnabled; |
| |
| CGFloat _totalHeightOfBanners; |
| } |
| |
| @end |
| |
| @implementation WKViewData |
| @end |
| |
| @interface WKResponderChainSink : NSResponder { |
| NSResponder *_lastResponderInChain; |
| bool _didReceiveUnhandledCommand; |
| } |
| - (id)initWithResponderChain:(NSResponder *)chain; |
| - (void)detach; |
| - (bool)didReceiveUnhandledCommand; |
| @end |
| |
| @interface WKView () <WebViewImplDelegate> |
| @end |
| |
| @implementation WKView |
| |
| #if WK_API_ENABLED |
| |
| - (id)initWithFrame:(NSRect)frame processGroup:(WKProcessGroup *)processGroup browsingContextGroup:(WKBrowsingContextGroup *)browsingContextGroup |
| { |
| return [self initWithFrame:frame contextRef:processGroup._contextRef pageGroupRef:browsingContextGroup._pageGroupRef relatedToPage:nil]; |
| } |
| |
| - (id)initWithFrame:(NSRect)frame processGroup:(WKProcessGroup *)processGroup browsingContextGroup:(WKBrowsingContextGroup *)browsingContextGroup relatedToView:(WKView *)relatedView |
| { |
| return [self initWithFrame:frame contextRef:processGroup._contextRef pageGroupRef:browsingContextGroup._pageGroupRef relatedToPage:relatedView ? toAPI(relatedView->_data->_page.get()) : nil]; |
| } |
| |
| #endif // WK_API_ENABLED |
| |
| - (void)dealloc |
| { |
| #if WK_API_ENABLED |
| if (_data->_remoteObjectRegistry) { |
| _data->_page->process().processPool().removeMessageReceiver(Messages::RemoteObjectRegistry::messageReceiverName(), _data->_page->pageID()); |
| [_data->_remoteObjectRegistry _invalidate]; |
| } |
| #endif |
| |
| _data->_page->close(); |
| |
| _data->_impl = nullptr; |
| |
| [_data release]; |
| _data = nil; |
| |
| NSNotificationCenter* workspaceNotificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter]; |
| [workspaceNotificationCenter removeObserver:self name:NSWorkspaceActiveSpaceDidChangeNotification object:nil]; |
| |
| WebProcessPool::statistics().wkViewCount--; |
| |
| [super dealloc]; |
| } |
| |
| #if WK_API_ENABLED |
| |
| - (WKBrowsingContextController *)browsingContextController |
| { |
| if (!_data->_browsingContextController) |
| _data->_browsingContextController = adoptNS([[WKBrowsingContextController alloc] _initWithPageRef:toAPI(_data->_page.get())]); |
| |
| return _data->_browsingContextController.get(); |
| } |
| |
| #endif // WK_API_ENABLED |
| |
| - (void)setDrawsBackground:(BOOL)drawsBackground |
| { |
| _data->_impl->setDrawsBackground(drawsBackground); |
| } |
| |
| - (BOOL)drawsBackground |
| { |
| return _data->_impl->drawsBackground(); |
| } |
| |
| - (void)setDrawsTransparentBackground:(BOOL)drawsTransparentBackground |
| { |
| _data->_impl->setDrawsTransparentBackground(drawsTransparentBackground); |
| } |
| |
| - (BOOL)drawsTransparentBackground |
| { |
| return _data->_impl->drawsTransparentBackground(); |
| } |
| |
| - (BOOL)acceptsFirstResponder |
| { |
| return _data->_impl->acceptsFirstResponder(); |
| } |
| |
| - (BOOL)becomeFirstResponder |
| { |
| return _data->_impl->becomeFirstResponder(); |
| } |
| |
| - (BOOL)resignFirstResponder |
| { |
| return _data->_impl->resignFirstResponder(); |
| } |
| |
| - (void)viewWillStartLiveResize |
| { |
| _data->_impl->viewWillStartLiveResize(); |
| } |
| |
| - (void)viewDidEndLiveResize |
| { |
| _data->_impl->viewDidEndLiveResize(); |
| } |
| |
| - (BOOL)isFlipped |
| { |
| return YES; |
| } |
| |
| - (NSSize)intrinsicContentSize |
| { |
| return NSSizeFromCGSize(_data->_impl->intrinsicContentSize()); |
| } |
| |
| - (void)prepareContentInRect:(NSRect)rect |
| { |
| _data->_impl->setContentPreparationRect(NSRectToCGRect(rect)); |
| _data->_impl->updateViewExposedRect(); |
| } |
| |
| - (void)setFrameSize:(NSSize)size |
| { |
| [super setFrameSize:size]; |
| _data->_impl->setFrameSize(NSSizeToCGSize(size)); |
| } |
| |
| - (void)renewGState |
| { |
| _data->_impl->renewGState(); |
| [super renewGState]; |
| } |
| |
| 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* createSelectorExceptionMap() |
| { |
| SelectorNameMap* map = new HashMap<SEL, String>; |
| |
| map->add(@selector(insertNewlineIgnoringFieldEditor:), "InsertNewline"); |
| map->add(@selector(insertParagraphSeparator:), "InsertNewline"); |
| map->add(@selector(insertTabIgnoringFieldEditor:), "InsertTab"); |
| map->add(@selector(pageDown:), "MovePageDown"); |
| map->add(@selector(pageDownAndModifySelection:), "MovePageDownAndModifySelection"); |
| map->add(@selector(pageUp:), "MovePageUp"); |
| map->add(@selector(pageUpAndModifySelection:), "MovePageUpAndModifySelection"); |
| map->add(@selector(scrollPageDown:), "ScrollPageForward"); |
| map->add(@selector(scrollPageUp:), "ScrollPageBackward"); |
| |
| return map; |
| } |
| |
| static String commandNameForSelector(SEL selector) |
| { |
| // Check the exception map first. |
| static const SelectorNameMap* exceptionMap = createSelectorExceptionMap(); |
| 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); |
| } |
| |
| // Editing commands |
| |
| #define WEBCORE_COMMAND(command) - (void)command:(id)sender { _data->_impl->executeEditCommand(commandNameForSelector(_cmd)); } |
| |
| WEBCORE_COMMAND(alignCenter) |
| WEBCORE_COMMAND(alignJustified) |
| WEBCORE_COMMAND(alignLeft) |
| WEBCORE_COMMAND(alignRight) |
| WEBCORE_COMMAND(copy) |
| WEBCORE_COMMAND(cut) |
| WEBCORE_COMMAND(delete) |
| WEBCORE_COMMAND(deleteBackward) |
| WEBCORE_COMMAND(deleteBackwardByDecomposingPreviousCharacter) |
| WEBCORE_COMMAND(deleteForward) |
| WEBCORE_COMMAND(deleteToBeginningOfLine) |
| WEBCORE_COMMAND(deleteToBeginningOfParagraph) |
| WEBCORE_COMMAND(deleteToEndOfLine) |
| WEBCORE_COMMAND(deleteToEndOfParagraph) |
| WEBCORE_COMMAND(deleteToMark) |
| WEBCORE_COMMAND(deleteWordBackward) |
| WEBCORE_COMMAND(deleteWordForward) |
| WEBCORE_COMMAND(ignoreSpelling) |
| WEBCORE_COMMAND(indent) |
| WEBCORE_COMMAND(insertBacktab) |
| WEBCORE_COMMAND(insertLineBreak) |
| WEBCORE_COMMAND(insertNewline) |
| WEBCORE_COMMAND(insertNewlineIgnoringFieldEditor) |
| WEBCORE_COMMAND(insertParagraphSeparator) |
| WEBCORE_COMMAND(insertTab) |
| WEBCORE_COMMAND(insertTabIgnoringFieldEditor) |
| WEBCORE_COMMAND(makeTextWritingDirectionLeftToRight) |
| WEBCORE_COMMAND(makeTextWritingDirectionNatural) |
| WEBCORE_COMMAND(makeTextWritingDirectionRightToLeft) |
| WEBCORE_COMMAND(moveBackward) |
| WEBCORE_COMMAND(moveBackwardAndModifySelection) |
| WEBCORE_COMMAND(moveDown) |
| WEBCORE_COMMAND(moveDownAndModifySelection) |
| WEBCORE_COMMAND(moveForward) |
| WEBCORE_COMMAND(moveForwardAndModifySelection) |
| WEBCORE_COMMAND(moveLeft) |
| WEBCORE_COMMAND(moveLeftAndModifySelection) |
| WEBCORE_COMMAND(moveParagraphBackwardAndModifySelection) |
| WEBCORE_COMMAND(moveParagraphForwardAndModifySelection) |
| WEBCORE_COMMAND(moveRight) |
| WEBCORE_COMMAND(moveRightAndModifySelection) |
| WEBCORE_COMMAND(moveToBeginningOfDocument) |
| WEBCORE_COMMAND(moveToBeginningOfDocumentAndModifySelection) |
| WEBCORE_COMMAND(moveToBeginningOfLine) |
| WEBCORE_COMMAND(moveToBeginningOfLineAndModifySelection) |
| WEBCORE_COMMAND(moveToBeginningOfParagraph) |
| WEBCORE_COMMAND(moveToBeginningOfParagraphAndModifySelection) |
| WEBCORE_COMMAND(moveToBeginningOfSentence) |
| WEBCORE_COMMAND(moveToBeginningOfSentenceAndModifySelection) |
| WEBCORE_COMMAND(moveToEndOfDocument) |
| WEBCORE_COMMAND(moveToEndOfDocumentAndModifySelection) |
| WEBCORE_COMMAND(moveToEndOfLine) |
| WEBCORE_COMMAND(moveToEndOfLineAndModifySelection) |
| WEBCORE_COMMAND(moveToEndOfParagraph) |
| WEBCORE_COMMAND(moveToEndOfParagraphAndModifySelection) |
| WEBCORE_COMMAND(moveToEndOfSentence) |
| WEBCORE_COMMAND(moveToEndOfSentenceAndModifySelection) |
| WEBCORE_COMMAND(moveToLeftEndOfLine) |
| WEBCORE_COMMAND(moveToLeftEndOfLineAndModifySelection) |
| WEBCORE_COMMAND(moveToRightEndOfLine) |
| WEBCORE_COMMAND(moveToRightEndOfLineAndModifySelection) |
| WEBCORE_COMMAND(moveUp) |
| WEBCORE_COMMAND(moveUpAndModifySelection) |
| WEBCORE_COMMAND(moveWordBackward) |
| WEBCORE_COMMAND(moveWordBackwardAndModifySelection) |
| WEBCORE_COMMAND(moveWordForward) |
| WEBCORE_COMMAND(moveWordForwardAndModifySelection) |
| WEBCORE_COMMAND(moveWordLeft) |
| WEBCORE_COMMAND(moveWordLeftAndModifySelection) |
| WEBCORE_COMMAND(moveWordRight) |
| WEBCORE_COMMAND(moveWordRightAndModifySelection) |
| WEBCORE_COMMAND(outdent) |
| WEBCORE_COMMAND(pageDown) |
| WEBCORE_COMMAND(pageDownAndModifySelection) |
| WEBCORE_COMMAND(pageUp) |
| WEBCORE_COMMAND(pageUpAndModifySelection) |
| WEBCORE_COMMAND(paste) |
| WEBCORE_COMMAND(pasteAsPlainText) |
| WEBCORE_COMMAND(scrollPageDown) |
| WEBCORE_COMMAND(scrollPageUp) |
| WEBCORE_COMMAND(scrollLineDown) |
| WEBCORE_COMMAND(scrollLineUp) |
| WEBCORE_COMMAND(scrollToBeginningOfDocument) |
| WEBCORE_COMMAND(scrollToEndOfDocument) |
| WEBCORE_COMMAND(selectAll) |
| WEBCORE_COMMAND(selectLine) |
| WEBCORE_COMMAND(selectParagraph) |
| WEBCORE_COMMAND(selectSentence) |
| WEBCORE_COMMAND(selectToMark) |
| WEBCORE_COMMAND(selectWord) |
| WEBCORE_COMMAND(setMark) |
| WEBCORE_COMMAND(subscript) |
| WEBCORE_COMMAND(superscript) |
| WEBCORE_COMMAND(swapWithMark) |
| WEBCORE_COMMAND(takeFindStringFromSelection) |
| WEBCORE_COMMAND(transpose) |
| WEBCORE_COMMAND(underline) |
| WEBCORE_COMMAND(unscript) |
| WEBCORE_COMMAND(yank) |
| WEBCORE_COMMAND(yankAndSelect) |
| |
| #undef WEBCORE_COMMAND |
| |
| // This method is needed to support Mac OS X services. |
| |
| - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pasteboard types:(NSArray *)types |
| { |
| return _data->_impl->writeSelectionToPasteboard(pasteboard, types); |
| } |
| |
| - (void)centerSelectionInVisibleArea:(id)sender |
| { |
| _data->_impl->centerSelectionInVisibleArea(); |
| } |
| |
| // This method is needed to support Mac OS X services. |
| |
| - (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)returnType |
| { |
| EditorState editorState = _data->_page->editorState(); |
| BOOL isValidSendType = NO; |
| |
| if (sendType && !editorState.selectionIsNone) { |
| if (editorState.isInPlugin) |
| isValidSendType = [sendType isEqualToString:NSStringPboardType]; |
| else |
| isValidSendType = [PasteboardTypes::forSelection() containsObject:sendType]; |
| } |
| |
| BOOL isValidReturnType = NO; |
| if (!returnType) |
| isValidReturnType = YES; |
| 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:NSStringPboardType]; |
| } |
| if (isValidSendType && isValidReturnType) |
| return self; |
| return [[self nextResponder] validRequestorForSendType:sendType returnType:returnType]; |
| } |
| |
| // This method is needed to support Mac OS X services. |
| |
| - (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pasteboard |
| { |
| return _data->_page->readSelectionFromPasteboard([pasteboard name]); |
| } |
| |
| // Font panel support. |
| |
| - (void)changeFont:(id)sender |
| { |
| NSFontManager *fontManager = [NSFontManager sharedFontManager]; |
| NSFont *font = [fontManager convertFont:[fontManager selectedFont]]; |
| if (!font) |
| return; |
| _data->_page->setFont([font familyName], [font pointSize], [[font fontDescriptor] symbolicTraits]); |
| } |
| |
| /* |
| |
| When possible, editing-related methods should be implemented in WebCore with the |
| EditorCommand mechanism and invoked via WEBCORE_COMMAND, rather than implementing |
| individual methods here with Mac-specific code. |
| |
| Editing-related methods still unimplemented that are implemented in WebKit1: |
| |
| - (void)complete:(id)sender; |
| - (void)copyFont:(id)sender; |
| - (void)makeBaseWritingDirectionLeftToRight:(id)sender; |
| - (void)makeBaseWritingDirectionNatural:(id)sender; |
| - (void)makeBaseWritingDirectionRightToLeft:(id)sender; |
| - (void)pasteFont:(id)sender; |
| - (void)scrollLineDown:(id)sender; |
| - (void)scrollLineUp:(id)sender; |
| - (void)showGuessPanel:(id)sender; |
| |
| Some other editing-related methods still unimplemented: |
| |
| - (void)changeCaseOfLetter:(id)sender; |
| - (void)copyRuler:(id)sender; |
| - (void)insertContainerBreak:(id)sender; |
| - (void)insertDoubleQuoteIgnoringSubstitution:(id)sender; |
| - (void)insertSingleQuoteIgnoringSubstitution:(id)sender; |
| - (void)pasteRuler:(id)sender; |
| - (void)toggleRuler:(id)sender; |
| - (void)transposeWords:(id)sender; |
| |
| */ |
| |
| // Menu items validation |
| |
| 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)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item |
| { |
| SEL action = [item action]; |
| |
| if (action == @selector(showGuessPanel:)) { |
| if (NSMenuItem *menuItem = ::menuItem(item)) |
| [menuItem setTitle:contextMenuItemTagShowSpellingPanel(![[[NSSpellChecker sharedSpellChecker] spellingPanel] isVisible])]; |
| return _data->_page->editorState().isContentEditable; |
| } |
| |
| if (action == @selector(checkSpelling:) || action == @selector(changeSpelling:)) |
| return _data->_page->editorState().isContentEditable; |
| |
| if (action == @selector(toggleContinuousSpellChecking:)) { |
| bool enabled = TextChecker::isContinuousSpellCheckingAllowed(); |
| bool checked = enabled && TextChecker::state().isContinuousSpellCheckingEnabled; |
| [menuItem(item) setState:checked ? NSOnState : NSOffState]; |
| return enabled; |
| } |
| |
| if (action == @selector(toggleGrammarChecking:)) { |
| bool checked = TextChecker::state().isGrammarCheckingEnabled; |
| [menuItem(item) setState:checked ? NSOnState : NSOffState]; |
| return YES; |
| } |
| |
| if (action == @selector(toggleAutomaticSpellingCorrection:)) { |
| bool checked = TextChecker::state().isAutomaticSpellingCorrectionEnabled; |
| [menuItem(item) setState:checked ? NSOnState : NSOffState]; |
| return _data->_page->editorState().isContentEditable; |
| } |
| |
| if (action == @selector(orderFrontSubstitutionsPanel:)) { |
| if (NSMenuItem *menuItem = ::menuItem(item)) |
| [menuItem setTitle:contextMenuItemTagShowSubstitutions(![[[NSSpellChecker sharedSpellChecker] substitutionsPanel] isVisible])]; |
| return _data->_page->editorState().isContentEditable; |
| } |
| |
| if (action == @selector(toggleSmartInsertDelete:)) { |
| bool checked = _data->_page->isSmartInsertDeleteEnabled(); |
| [menuItem(item) setState:checked ? NSOnState : NSOffState]; |
| return _data->_page->editorState().isContentEditable; |
| } |
| |
| if (action == @selector(toggleAutomaticQuoteSubstitution:)) { |
| bool checked = TextChecker::state().isAutomaticQuoteSubstitutionEnabled; |
| [menuItem(item) setState:checked ? NSOnState : NSOffState]; |
| return _data->_page->editorState().isContentEditable; |
| } |
| |
| if (action == @selector(toggleAutomaticDashSubstitution:)) { |
| bool checked = TextChecker::state().isAutomaticDashSubstitutionEnabled; |
| [menuItem(item) setState:checked ? NSOnState : NSOffState]; |
| return _data->_page->editorState().isContentEditable; |
| } |
| |
| if (action == @selector(toggleAutomaticLinkDetection:)) { |
| bool checked = TextChecker::state().isAutomaticLinkDetectionEnabled; |
| [menuItem(item) setState:checked ? NSOnState : NSOffState]; |
| return _data->_page->editorState().isContentEditable; |
| } |
| |
| if (action == @selector(toggleAutomaticTextReplacement:)) { |
| bool checked = TextChecker::state().isAutomaticTextReplacementEnabled; |
| [menuItem(item) setState:checked ? NSOnState : NSOffState]; |
| return _data->_page->editorState().isContentEditable; |
| } |
| |
| if (action == @selector(uppercaseWord:) || action == @selector(lowercaseWord:) || action == @selector(capitalizeWord:)) |
| return _data->_page->editorState().selectionIsRange && _data->_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 _data->_page->editorState().selectionIsRange || (_data->_page->editorState().isContentEditable && !_data->_page->editorState().selectionIsNone); |
| |
| // Next, handle editor commands. Start by returning YES for anything that is not an editor command. |
| // Returning YES is the default thing to do in an AppKit validate method for any selector that is not recognized. |
| String commandName = commandNameForSelector([item action]); |
| if (!Editor::commandIsSupportedFromMenuOrKeyBinding(commandName)) |
| return YES; |
| |
| // Add this item to the vector of items for a given command that are awaiting validation. |
| ValidationMap::AddResult addResult = _data->_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. |
| _data->_page->validateCommand(commandName, [self](const String& commandName, bool isEnabled, int32_t state, WebKit::CallbackBase::Error error) { |
| // 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; |
| |
| [self _setUserInterfaceItemState:commandName enabled:isEnabled state: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 YES; |
| } |
| |
| - (IBAction)startSpeaking:(id)sender |
| { |
| _data->_page->getSelectionOrContentsAsString([self](const String& string, WebKit::CallbackBase::Error error) { |
| if (error != WebKit::CallbackBase::Error::None) |
| return; |
| if (!string) |
| return; |
| |
| [NSApp speakString:string]; |
| }); |
| } |
| |
| - (IBAction)stopSpeaking:(id)sender |
| { |
| [NSApp stopSpeaking:sender]; |
| } |
| |
| - (IBAction)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; |
| } |
| |
| _data->_page->advanceToNextMisspelling(true); |
| [spellingPanel orderFront:sender]; |
| } |
| |
| - (IBAction)checkSpelling:(id)sender |
| { |
| _data->_page->advanceToNextMisspelling(false); |
| } |
| |
| - (void)changeSpelling:(id)sender |
| { |
| NSString *word = [[sender selectedCell] stringValue]; |
| |
| _data->_page->changeSpellingToWord(word); |
| } |
| |
| - (IBAction)toggleContinuousSpellChecking:(id)sender |
| { |
| bool spellCheckingEnabled = !TextChecker::state().isContinuousSpellCheckingEnabled; |
| TextChecker::setContinuousSpellCheckingEnabled(spellCheckingEnabled); |
| |
| _data->_page->process().updateTextCheckerState(); |
| } |
| |
| - (BOOL)isGrammarCheckingEnabled |
| { |
| return TextChecker::state().isGrammarCheckingEnabled; |
| } |
| |
| - (void)setGrammarCheckingEnabled:(BOOL)flag |
| { |
| if (static_cast<bool>(flag) == TextChecker::state().isGrammarCheckingEnabled) |
| return; |
| |
| TextChecker::setGrammarCheckingEnabled(flag); |
| _data->_page->process().updateTextCheckerState(); |
| } |
| |
| - (IBAction)toggleGrammarChecking:(id)sender |
| { |
| bool grammarCheckingEnabled = !TextChecker::state().isGrammarCheckingEnabled; |
| TextChecker::setGrammarCheckingEnabled(grammarCheckingEnabled); |
| |
| _data->_page->process().updateTextCheckerState(); |
| } |
| |
| - (IBAction)toggleAutomaticSpellingCorrection:(id)sender |
| { |
| TextChecker::setAutomaticSpellingCorrectionEnabled(!TextChecker::state().isAutomaticSpellingCorrectionEnabled); |
| |
| _data->_page->process().updateTextCheckerState(); |
| } |
| |
| - (void)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]; |
| } |
| |
| - (IBAction)toggleSmartInsertDelete:(id)sender |
| { |
| _data->_page->setSmartInsertDeleteEnabled(!_data->_page->isSmartInsertDeleteEnabled()); |
| } |
| |
| - (BOOL)isAutomaticQuoteSubstitutionEnabled |
| { |
| return TextChecker::state().isAutomaticQuoteSubstitutionEnabled; |
| } |
| |
| - (void)setAutomaticQuoteSubstitutionEnabled:(BOOL)flag |
| { |
| if (static_cast<bool>(flag) == TextChecker::state().isAutomaticQuoteSubstitutionEnabled) |
| return; |
| |
| TextChecker::setAutomaticQuoteSubstitutionEnabled(flag); |
| _data->_page->process().updateTextCheckerState(); |
| } |
| |
| - (void)toggleAutomaticQuoteSubstitution:(id)sender |
| { |
| TextChecker::setAutomaticQuoteSubstitutionEnabled(!TextChecker::state().isAutomaticQuoteSubstitutionEnabled); |
| _data->_page->process().updateTextCheckerState(); |
| } |
| |
| - (BOOL)isAutomaticDashSubstitutionEnabled |
| { |
| return TextChecker::state().isAutomaticDashSubstitutionEnabled; |
| } |
| |
| - (void)setAutomaticDashSubstitutionEnabled:(BOOL)flag |
| { |
| if (static_cast<bool>(flag) == TextChecker::state().isAutomaticDashSubstitutionEnabled) |
| return; |
| |
| TextChecker::setAutomaticDashSubstitutionEnabled(flag); |
| _data->_page->process().updateTextCheckerState(); |
| } |
| |
| - (void)toggleAutomaticDashSubstitution:(id)sender |
| { |
| TextChecker::setAutomaticDashSubstitutionEnabled(!TextChecker::state().isAutomaticDashSubstitutionEnabled); |
| _data->_page->process().updateTextCheckerState(); |
| } |
| |
| - (BOOL)isAutomaticLinkDetectionEnabled |
| { |
| return TextChecker::state().isAutomaticLinkDetectionEnabled; |
| } |
| |
| - (void)setAutomaticLinkDetectionEnabled:(BOOL)flag |
| { |
| if (static_cast<bool>(flag) == TextChecker::state().isAutomaticLinkDetectionEnabled) |
| return; |
| |
| TextChecker::setAutomaticLinkDetectionEnabled(flag); |
| _data->_page->process().updateTextCheckerState(); |
| } |
| |
| - (void)toggleAutomaticLinkDetection:(id)sender |
| { |
| TextChecker::setAutomaticLinkDetectionEnabled(!TextChecker::state().isAutomaticLinkDetectionEnabled); |
| _data->_page->process().updateTextCheckerState(); |
| } |
| |
| - (BOOL)isAutomaticTextReplacementEnabled |
| { |
| return TextChecker::state().isAutomaticTextReplacementEnabled; |
| } |
| |
| - (void)setAutomaticTextReplacementEnabled:(BOOL)flag |
| { |
| if (static_cast<bool>(flag) == TextChecker::state().isAutomaticTextReplacementEnabled) |
| return; |
| |
| TextChecker::setAutomaticTextReplacementEnabled(flag); |
| _data->_page->process().updateTextCheckerState(); |
| } |
| |
| - (void)toggleAutomaticTextReplacement:(id)sender |
| { |
| TextChecker::setAutomaticTextReplacementEnabled(!TextChecker::state().isAutomaticTextReplacementEnabled); |
| _data->_page->process().updateTextCheckerState(); |
| } |
| |
| - (void)uppercaseWord:(id)sender |
| { |
| _data->_page->uppercaseWord(); |
| } |
| |
| - (void)lowercaseWord:(id)sender |
| { |
| _data->_page->lowercaseWord(); |
| } |
| |
| - (void)capitalizeWord:(id)sender |
| { |
| _data->_page->capitalizeWord(); |
| } |
| |
| // Events |
| |
| // Override this so that AppKit will send us arrow keys as key down events so we can |
| // support them via the key bindings mechanism. |
| - (BOOL)_wantsKeyDownForEvent:(NSEvent *)event |
| { |
| return YES; |
| } |
| |
| - (void)_setMouseDownEvent:(NSEvent *)event |
| { |
| ASSERT(!event || [event type] == NSLeftMouseDown || [event type] == NSRightMouseDown || [event type] == NSOtherMouseDown); |
| |
| if (event == _data->_mouseDownEvent) |
| return; |
| |
| [_data->_mouseDownEvent release]; |
| _data->_mouseDownEvent = [event retain]; |
| } |
| |
| #if USE(ASYNC_NSTEXTINPUTCLIENT) |
| #define NATIVE_MOUSE_EVENT_HANDLER(Selector) \ |
| - (void)Selector:(NSEvent *)theEvent \ |
| { \ |
| if (_data->_impl->ignoresNonWheelEvents()) \ |
| return; \ |
| if (NSTextInputContext *context = [self inputContext]) { \ |
| [context handleEvent:theEvent completionHandler:^(BOOL handled) { \ |
| if (handled) \ |
| LOG(TextInput, "%s was handled by text input context", String(#Selector).substring(0, String(#Selector).find("Internal")).ascii().data()); \ |
| else { \ |
| NativeWebMouseEvent webEvent(theEvent, _data->_impl->lastPressureEvent(), self); \ |
| _data->_page->handleMouseEvent(webEvent); \ |
| } \ |
| }]; \ |
| return; \ |
| } \ |
| NativeWebMouseEvent webEvent(theEvent, _data->_impl->lastPressureEvent(), self); \ |
| _data->_page->handleMouseEvent(webEvent); \ |
| } |
| #define NATIVE_MOUSE_EVENT_HANDLER_INTERNAL(Selector) \ |
| - (void)Selector:(NSEvent *)theEvent \ |
| { \ |
| if (_data->_impl->ignoresNonWheelEvents()) \ |
| return; \ |
| if (NSTextInputContext *context = [self inputContext]) { \ |
| [context handleEvent:theEvent completionHandler:^(BOOL handled) { \ |
| if (handled) \ |
| LOG(TextInput, "%s was handled by text input context", String(#Selector).substring(0, String(#Selector).find("Internal")).ascii().data()); \ |
| else { \ |
| NativeWebMouseEvent webEvent(theEvent, _data->_impl->lastPressureEvent(), self); \ |
| _data->_page->handleMouseEvent(webEvent); \ |
| } \ |
| }]; \ |
| return; \ |
| } \ |
| NativeWebMouseEvent webEvent(theEvent, _data->_impl->lastPressureEvent(), self); \ |
| _data->_page->handleMouseEvent(webEvent); \ |
| } |
| #else |
| #define NATIVE_MOUSE_EVENT_HANDLER(Selector) \ |
| - (void)Selector:(NSEvent *)theEvent \ |
| { \ |
| if (_data->_impl->ignoresNonWheelEvents()) \ |
| return; \ |
| if ([[self inputContext] handleEvent:theEvent]) { \ |
| LOG(TextInput, "%s was handled by text input context", String(#Selector).substring(0, String(#Selector).find("Internal")).ascii().data()); \ |
| return; \ |
| } \ |
| NativeWebMouseEvent webEvent(theEvent, _data->_impl->lastPressureEvent(), self); \ |
| _data->_page->handleMouseEvent(webEvent); \ |
| } |
| #define NATIVE_MOUSE_EVENT_HANDLER_INTERNAL(Selector) \ |
| - (void)Selector:(NSEvent *)theEvent \ |
| { \ |
| if (_data->_impl->ignoresNonWheelEvents()) \ |
| return; \ |
| if ([[self inputContext] handleEvent:theEvent]) { \ |
| LOG(TextInput, "%s was handled by text input context", String(#Selector).substring(0, String(#Selector).find("Internal")).ascii().data()); \ |
| return; \ |
| } \ |
| NativeWebMouseEvent webEvent(theEvent, _data->_impl->lastPressureEvent(), self); \ |
| _data->_page->handleMouseEvent(webEvent); \ |
| } |
| #endif |
| |
| 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 |
| |
| - (void)scrollWheel:(NSEvent *)event |
| { |
| _data->_impl->scrollWheel(event); |
| } |
| |
| - (void)swipeWithEvent:(NSEvent *)event |
| { |
| _data->_impl->swipeWithEvent(event); |
| } |
| |
| - (void)mouseMoved:(NSEvent *)event |
| { |
| if (_data->_impl->ignoresNonWheelEvents()) |
| return; |
| |
| // When a view is first responder, it gets mouse moved events even when the mouse is outside its visible rect. |
| if (self == [[self window] firstResponder] && !NSPointInRect([self convertPoint:[event locationInWindow] fromView:nil], [self visibleRect])) |
| return; |
| |
| [self mouseMovedInternal:event]; |
| } |
| |
| - (void)mouseDown:(NSEvent *)event |
| { |
| if (_data->_impl->ignoresNonWheelEvents()) |
| return; |
| |
| [self _setMouseDownEvent:event]; |
| _data->_impl->setIgnoresMouseDraggedEvents(false); |
| |
| [self mouseDownInternal:event]; |
| } |
| |
| - (void)mouseUp:(NSEvent *)event |
| { |
| if (_data->_impl->ignoresNonWheelEvents()) |
| return; |
| |
| [self _setMouseDownEvent:nil]; |
| [self mouseUpInternal:event]; |
| } |
| |
| - (void)mouseDragged:(NSEvent *)event |
| { |
| if (_data->_impl->ignoresNonWheelEvents()) |
| return; |
| if (_data->_impl->ignoresMouseDraggedEvents()) |
| return; |
| |
| [self mouseDraggedInternal:event]; |
| } |
| |
| - (void)pressureChangeWithEvent:(NSEvent *)event |
| { |
| _data->_impl->pressureChangeWithEvent(event); |
| } |
| |
| - (BOOL)acceptsFirstMouse:(NSEvent *)event |
| { |
| // 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. |
| [[event retain] autorelease]; |
| |
| if (![self hitTest:[event locationInWindow]]) |
| return NO; |
| |
| [self _setMouseDownEvent:event]; |
| bool result = _data->_page->acceptsFirstMouse([event eventNumber], WebEventFactory::createWebMouseEvent(event, _data->_impl->lastPressureEvent(), self)); |
| [self _setMouseDownEvent:nil]; |
| return result; |
| } |
| |
| - (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent *)event |
| { |
| // If this is the active window or we don't have a range selection, there is no need to perform additional checks |
| // and we can avoid making a synchronous call to the WebProcess. |
| if ([[self window] isKeyWindow] || _data->_page->editorState().selectionIsNone || !_data->_page->editorState().selectionIsRange) |
| 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. |
| [[event retain] autorelease]; |
| |
| if (![self hitTest:[event locationInWindow]]) |
| return NO; |
| |
| [self _setMouseDownEvent:event]; |
| bool result = _data->_page->shouldDelayWindowOrderingForEvent(WebEventFactory::createWebMouseEvent(event, _data->_impl->lastPressureEvent(), self)); |
| [self _setMouseDownEvent:nil]; |
| return result; |
| } |
| |
| static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnderline>& result) |
| { |
| int length = [[string string] length]; |
| |
| int i = 0; |
| while (i < length) { |
| NSRange range; |
| NSDictionary *attrs = [string attributesAtIndex:i longestEffectiveRange:&range inRange:NSMakeRange(i, length - i)]; |
| |
| if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) { |
| Color color = Color::black; |
| if (NSColor *colorAttr = [attrs objectForKey:NSUnderlineColorAttributeName]) |
| color = colorFromNSColor([colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]); |
| result.append(CompositionUnderline(range.location, NSMaxRange(range), color, [style intValue] > 1)); |
| } |
| |
| i = range.location + range.length; |
| } |
| } |
| |
| #if USE(ASYNC_NSTEXTINPUTCLIENT) |
| |
| - (void)_collectKeyboardLayoutCommandsForEvent:(NSEvent *)event to:(Vector<KeypressCommand>&)commands |
| { |
| if ([event type] != NSKeyDown) |
| return; |
| |
| ASSERT(!_data->_collectedKeypressCommands); |
| _data->_collectedKeypressCommands = &commands; |
| |
| if (NSTextInputContext *context = [self inputContext]) |
| [context handleEventByKeyboardLayout:event]; |
| else |
| [self interpretKeyEvents:[NSArray arrayWithObject:event]]; |
| |
| _data->_collectedKeypressCommands = nullptr; |
| } |
| |
| - (void)_interpretKeyEvent:(NSEvent *)event completionHandler:(void(^)(BOOL handled, const Vector<KeypressCommand>& commands))completionHandler |
| { |
| // 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 (_data->_impl->pluginComplexTextInputIdentifier()) { |
| completionHandler(NO, Vector<KeypressCommand>()); |
| return; |
| } |
| |
| if (![self inputContext]) { |
| Vector<KeypressCommand> commands; |
| [self _collectKeyboardLayoutCommandsForEvent:event to:commands]; |
| completionHandler(NO, commands); |
| return; |
| } |
| |
| LOG(TextInput, "-> handleEventByInputMethod:%p %@", event, event); |
| [[self inputContext] handleEventByInputMethod:event completionHandler:^(BOOL handled) { |
| |
| LOG(TextInput, "... handleEventByInputMethod%s handled", handled ? "" : " not"); |
| if (handled) { |
| completionHandler(YES, Vector<KeypressCommand>()); |
| return; |
| } |
| |
| Vector<KeypressCommand> commands; |
| [self _collectKeyboardLayoutCommandsForEvent:event to:commands]; |
| completionHandler(NO, commands); |
| }]; |
| } |
| |
| - (void)doCommandBySelector:(SEL)selector |
| { |
| LOG(TextInput, "doCommandBySelector:\"%s\"", sel_getName(selector)); |
| |
| Vector<KeypressCommand>* keypressCommands = _data->_collectedKeypressCommands; |
| |
| if (keypressCommands) { |
| KeypressCommand command(NSStringFromSelector(selector)); |
| keypressCommands->append(command); |
| LOG(TextInput, "...stored"); |
| _data->_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. |
| [super doCommandBySelector:selector]; |
| } |
| } |
| |
| - (void)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. |
| [self insertText:string replacementRange:NSMakeRange(NSNotFound, 0)]; |
| } |
| |
| - (void)insertText:(id)string replacementRange:(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<TextAlternativeWithRange> dictationAlternatives; |
| |
| bool registerUndoGroup = false; |
| if (isAttributedString) { |
| #if USE(DICTATION_ALTERNATIVES) |
| collectDictationTextAlternatives(string, dictationAlternatives); |
| #endif |
| #if USE(INSERTION_UNDO_GROUPING) |
| registerUndoGroup = 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; |
| |
| // 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<KeypressCommand>* keypressCommands = _data->_collectedKeypressCommands; |
| if (keypressCommands) { |
| ASSERT(replacementRange.location == NSNotFound); |
| KeypressCommand command("insertText:", text); |
| keypressCommands->append(command); |
| LOG(TextInput, "...stored"); |
| _data->_page->registerKeypressCommandName(command.commandName); |
| return; |
| } |
| |
| String eventText = text; |
| eventText.replace(NSBackTabCharacter, NSTabCharacter); // same thing is done in KeyEventMac.mm in WebCore |
| if (!dictationAlternatives.isEmpty()) |
| _data->_page->insertDictatedTextAsync(eventText, replacementRange, dictationAlternatives, registerUndoGroup); |
| else |
| _data->_page->insertTextAsync(eventText, replacementRange, registerUndoGroup); |
| } |
| |
| - (void)selectedRangeWithCompletionHandler:(void(^)(NSRange selectedRange))completionHandlerPtr |
| { |
| RetainPtr<id> completionHandler = adoptNS([completionHandlerPtr copy]); |
| |
| LOG(TextInput, "selectedRange"); |
| _data->_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)markedRangeWithCompletionHandler:(void(^)(NSRange markedRange))completionHandlerPtr |
| { |
| RetainPtr<id> completionHandler = adoptNS([completionHandlerPtr copy]); |
| |
| LOG(TextInput, "markedRange"); |
| _data->_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)hasMarkedTextWithCompletionHandler:(void(^)(BOOL hasMarkedText))completionHandlerPtr |
| { |
| RetainPtr<id> completionHandler = adoptNS([completionHandlerPtr copy]); |
| |
| LOG(TextInput, "hasMarkedText"); |
| _data->_page->getMarkedRangeAsync([completionHandler](const EditingRange& editingRangeResult, WebKit::CallbackBase::Error error) { |
| void (^completionHandlerBlock)(BOOL) = (void (^)(BOOL))completionHandler.get(); |
| if (error != WebKit::CallbackBase::Error::None) { |
| LOG(TextInput, " ...hasMarkedText failed."); |
| completionHandlerBlock(NO); |
| return; |
| } |
| BOOL hasMarkedText = editingRangeResult.location != notFound; |
| LOG(TextInput, " -> hasMarkedText returned %u", hasMarkedText); |
| completionHandlerBlock(hasMarkedText); |
| }); |
| } |
| |
| - (void)attributedSubstringForProposedRange:(NSRange)nsRange completionHandler:(void(^)(NSAttributedString *attrString, NSRange actualRange))completionHandlerPtr |
| { |
| RetainPtr<id> completionHandler = adoptNS([completionHandlerPtr copy]); |
| |
| LOG(TextInput, "attributedSubstringFromRange:(%u, %u)", nsRange.location, nsRange.length); |
| _data->_page->attributedSubstringForCharacterRangeAsync(nsRange, [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; |
| } |
| LOG(TextInput, " -> attributedSubstringFromRange returned %@", [string.string.get() string]); |
| completionHandlerBlock([[string.string.get() retain] autorelease], actualRange); |
| }); |
| } |
| |
| - (void)firstRectForCharacterRange:(NSRange)theRange completionHandler:(void(^)(NSRect firstRect, NSRange actualRange))completionHandlerPtr |
| { |
| RetainPtr<id> completionHandler = adoptNS([completionHandlerPtr copy]); |
| |
| LOG(TextInput, "firstRectForCharacterRange:(%u, %u)", theRange.location, theRange.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 ((theRange.location + theRange.length < theRange.location) && (theRange.location + theRange.length != 0)) |
| theRange.length = 0; |
| |
| if (theRange.location == NSNotFound) { |
| LOG(TextInput, " -> NSZeroRect"); |
| completionHandlerPtr(NSZeroRect, theRange); |
| return; |
| } |
| |
| _data->_page->firstRectForCharacterRangeAsync(theRange, [self, completionHandler](const IntRect& rect, const EditingRange& actualRange, WebKit::CallbackBase::Error error) { |
| void (^completionHandlerBlock)(NSRect, NSRange) = (void (^)(NSRect, NSRange))completionHandler.get(); |
| if (error != WebKit::CallbackBase::Error::None) { |
| LOG(TextInput, " ...firstRectForCharacterRange failed."); |
| completionHandlerBlock(NSZeroRect, NSMakeRange(NSNotFound, 0)); |
| return; |
| } |
| |
| NSRect resultRect = [self convertRect:rect toView:nil]; |
| resultRect = [self.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)characterIndexForPoint:(NSPoint)thePoint completionHandler:(void(^)(NSUInteger))completionHandlerPtr |
| { |
| RetainPtr<id> completionHandler = adoptNS([completionHandlerPtr copy]); |
| |
| LOG(TextInput, "characterIndexForPoint:(%f, %f)", thePoint.x, thePoint.y); |
| |
| NSWindow *window = [self window]; |
| |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Wdeprecated-declarations" |
| if (window) |
| thePoint = [window convertScreenToBase:thePoint]; |
| #pragma clang diagnostic pop |
| thePoint = [self convertPoint:thePoint fromView:nil]; // the point is relative to the main frame |
| |
| _data->_page->characterIndexForPointAsync(IntPoint(thePoint), [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 *)inputContext |
| { |
| if (_data->_impl->pluginComplexTextInputIdentifier()) { |
| ASSERT(!_data->_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 (!_data->_page->editorState().isContentEditable) |
| return nil; |
| |
| return [super inputContext]; |
| } |
| |
| - (void)unmarkText |
| { |
| LOG(TextInput, "unmarkText"); |
| |
| _data->_page->confirmCompositionAsync(); |
| } |
| |
| - (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(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<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]; |
| extractUnderlines(string, underlines); |
| } else |
| text = string; |
| |
| if (_data->_impl->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(!_data->_page->editorState().hasComposition); |
| _data->_impl->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])) |
| _data->_page->insertTextAsync(text, replacementRange); |
| else |
| NSBeep(); |
| return; |
| } |
| |
| _data->_page->setCompositionAsync(text, underlines, selectedRange, replacementRange); |
| } |
| |
| // Synchronous NSTextInputClient is still implemented to catch spurious sync calls. Remove when that is no longer needed. |
| |
| - (NSRange)selectedRange NO_RETURN_DUE_TO_ASSERT |
| { |
| ASSERT_NOT_REACHED(); |
| return NSMakeRange(NSNotFound, 0); |
| } |
| |
| - (BOOL)hasMarkedText NO_RETURN_DUE_TO_ASSERT |
| { |
| ASSERT_NOT_REACHED(); |
| return NO; |
| } |
| |
| - (NSRange)markedRange NO_RETURN_DUE_TO_ASSERT |
| { |
| ASSERT_NOT_REACHED(); |
| return NSMakeRange(NSNotFound, 0); |
| } |
| |
| - (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)nsRange actualRange:(NSRangePointer)actualRange NO_RETURN_DUE_TO_ASSERT |
| { |
| ASSERT_NOT_REACHED(); |
| return nil; |
| } |
| |
| - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint NO_RETURN_DUE_TO_ASSERT |
| { |
| ASSERT_NOT_REACHED(); |
| return 0; |
| } |
| |
| - (NSRect)firstRectForCharacterRange:(NSRange)theRange actualRange:(NSRangePointer)actualRange NO_RETURN_DUE_TO_ASSERT |
| { |
| ASSERT_NOT_REACHED(); |
| return NSMakeRect(0, 0, 0, 0); |
| } |
| |
| - (BOOL)performKeyEquivalent:(NSEvent *)event |
| { |
| if (_data->_impl->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. |
| [[event retain] autorelease]; |
| |
| // 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] & NSDeviceIndependentModifierFlagsMask)) |
| return [super performKeyEquivalent:event]; |
| |
| if (_data->_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 [super performKeyEquivalent:event]; |
| } |
| |
| ASSERT(event == [NSApp currentEvent]); |
| |
| _data->_impl->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 (self == [[self window] firstResponder]) { |
| [self _interpretKeyEvent:event completionHandler:^(BOOL handledByInputMethod, const Vector<KeypressCommand>& commands) { |
| _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(event, handledByInputMethod, commands)); |
| }]; |
| return YES; |
| } |
| |
| return [super performKeyEquivalent:event]; |
| } |
| |
| - (void)keyUp:(NSEvent *)theEvent |
| { |
| if (_data->_impl->ignoresNonWheelEvents()) |
| return; |
| |
| LOG(TextInput, "keyUp:%p %@", theEvent, theEvent); |
| |
| [self _interpretKeyEvent:theEvent completionHandler:^(BOOL handledByInputMethod, const Vector<KeypressCommand>& commands) { |
| ASSERT(!handledByInputMethod || commands.isEmpty()); |
| _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent, handledByInputMethod, commands)); |
| }]; |
| } |
| |
| - (void)keyDown:(NSEvent *)theEvent |
| { |
| if (_data->_impl->ignoresNonWheelEvents()) |
| return; |
| |
| LOG(TextInput, "keyDown:%p %@%s", theEvent, theEvent, (theEvent == _data->_keyDownEventBeingResent) ? " (re-sent)" : ""); |
| |
| if (_data->_impl->tryHandlePluginComplexTextInputKeyDown(theEvent)) { |
| 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 (_data->_keyDownEventBeingResent == theEvent) { |
| [super keyDown:theEvent]; |
| return; |
| } |
| |
| [self _interpretKeyEvent:theEvent completionHandler:^(BOOL handledByInputMethod, const Vector<KeypressCommand>& commands) { |
| ASSERT(!handledByInputMethod || commands.isEmpty()); |
| _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent, handledByInputMethod, commands)); |
| }]; |
| } |
| |
| - (void)flagsChanged:(NSEvent *)theEvent |
| { |
| if (_data->_impl->ignoresNonWheelEvents()) |
| return; |
| |
| LOG(TextInput, "flagsChanged:%p %@", theEvent, theEvent); |
| |
| unsigned short keyCode = [theEvent keyCode]; |
| |
| // Don't make an event from the num lock and function keys |
| if (!keyCode || keyCode == 10 || keyCode == 63) |
| return; |
| |
| [self _interpretKeyEvent:theEvent completionHandler:^(BOOL handledByInputMethod, const Vector<KeypressCommand>& commands) { |
| _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent, handledByInputMethod, commands)); |
| }]; |
| } |
| |
| #else // USE(ASYNC_NSTEXTINPUTCLIENT) |
| |
| - (BOOL)_interpretKeyEvent:(NSEvent *)event savingCommandsTo:(Vector<WebCore::KeypressCommand>&)commands |
| { |
| ASSERT(!_data->_interpretKeyEventsParameters); |
| ASSERT(commands.isEmpty()); |
| |
| if ([event type] == NSFlagsChanged) |
| return NO; |
| |
| WKViewInterpretKeyEventsParameters parameters; |
| parameters.eventInterpretationHadSideEffects = false; |
| parameters.executingSavedKeypressCommands = false; |
| // We assume that an input method has consumed the event, and only change this assumption if one of the NSTextInput methods is called. |
| // We assume the IM will *not* consume hotkey sequences. |
| parameters.consumedByIM = !([event modifierFlags] & NSCommandKeyMask); |
| parameters.commands = &commands; |
| _data->_interpretKeyEventsParameters = ¶meters; |
| |
| LOG(TextInput, "-> interpretKeyEvents:%p %@", event, event); |
| [self interpretKeyEvents:[NSArray arrayWithObject:event]]; |
| |
| _data->_interpretKeyEventsParameters = nullptr; |
| |
| // An input method may consume an event and not tell us (e.g. when displaying a candidate window), |
| // in which case we should not bubble the event up the DOM. |
| if (parameters.consumedByIM) { |
| ASSERT(commands.isEmpty()); |
| LOG(TextInput, "...event %p was consumed by an input method", event); |
| return YES; |
| } |
| |
| LOG(TextInput, "...interpretKeyEvents for event %p done, returns %d", event, parameters.eventInterpretationHadSideEffects); |
| |
| // If we have already executed all or some of the commands, the event is "handled". Note that there are additional checks on web process side. |
| return parameters.eventInterpretationHadSideEffects; |
| } |
| |
| - (void)_executeSavedKeypressCommands |
| { |
| WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters; |
| if (!parameters || parameters->commands->isEmpty()) |
| return; |
| |
| // We could be called again if the execution of one command triggers a call to selectedRange. |
| // In this case, the state is up to date, and we don't need to execute any more saved commands to return a result. |
| if (parameters->executingSavedKeypressCommands) |
| return; |
| |
| LOG(TextInput, "Executing %u saved keypress commands...", parameters->commands->size()); |
| |
| parameters->executingSavedKeypressCommands = true; |
| parameters->eventInterpretationHadSideEffects |= _data->_page->executeKeypressCommands(*parameters->commands); |
| parameters->commands->clear(); |
| parameters->executingSavedKeypressCommands = false; |
| |
| LOG(TextInput, "...done executing saved keypress commands."); |
| } |
| |
| - (void)doCommandBySelector:(SEL)selector |
| { |
| LOG(TextInput, "doCommandBySelector:\"%s\"", sel_getName(selector)); |
| |
| WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters; |
| if (parameters) |
| parameters->consumedByIM = false; |
| |
| // As in insertText:replacementRange:, we assume that the call comes from an input method if there is marked text. |
| bool isFromInputMethod = _data->_page->editorState().hasComposition; |
| |
| if (parameters && !isFromInputMethod) { |
| KeypressCommand command(NSStringFromSelector(selector)); |
| parameters->commands->append(command); |
| LOG(TextInput, "...stored"); |
| _data->_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. |
| [super doCommandBySelector:selector]; |
| } |
| } |
| |
| - (void)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. |
| [self insertText:string replacementRange:NSMakeRange(NSNotFound, 0)]; |
| } |
| |
| - (void)insertText:(id)string replacementRange:(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); |
| WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters; |
| if (parameters) |
| parameters->consumedByIM = false; |
| |
| NSString *text; |
| bool isFromInputMethod = _data->_page->editorState().hasComposition; |
| |
| Vector<TextAlternativeWithRange> dictationAlternatives; |
| |
| if (isAttributedString) { |
| #if USE(DICTATION_ALTERNATIVES) |
| collectDictationTextAlternatives(string, dictationAlternatives); |
| #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; |
| |
| // insertText can be called for several reasons: |
| // - If it's from normal key event processing (including key bindings), we may need to save the action to perform it later. |
| // - If it's from an input method, then we should insert the text now. We assume it's from the input method if we have marked text. |
| // FIXME: In theory, this could be wrong for some input methods, so we should try to find another way to determine if the call is from the input method. |
| // - 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. |
| if (parameters && !isFromInputMethod) { |
| // FIXME: Handle replacementRange in this case, too. It's known to occur in practice when canceling Press and Hold (see <rdar://11940670>). |
| ASSERT(replacementRange.location == NSNotFound); |
| KeypressCommand command("insertText:", text); |
| parameters->commands->append(command); |
| _data->_page->registerKeypressCommandName(command.commandName); |
| return; |
| } |
| |
| String eventText = text; |
| eventText.replace(NSBackTabCharacter, NSTabCharacter); // same thing is done in KeyEventMac.mm in WebCore |
| bool eventHandled; |
| if (!dictationAlternatives.isEmpty()) |
| eventHandled = _data->_page->insertDictatedText(eventText, replacementRange, dictationAlternatives); |
| else |
| eventHandled = _data->_page->insertText(eventText, replacementRange); |
| |
| if (parameters) |
| parameters->eventInterpretationHadSideEffects |= eventHandled; |
| } |
| |
| - (NSTextInputContext *)inputContext |
| { |
| WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters; |
| |
| if (_data->_impl->pluginComplexTextInputIdentifier() && !parameters) |
| 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 (!_data->_page->editorState().isContentEditable) |
| return nil; |
| |
| return [super inputContext]; |
| } |
| |
| - (NSRange)selectedRange |
| { |
| [self _executeSavedKeypressCommands]; |
| |
| EditingRange selectedRange; |
| _data->_page->getSelectedRange(selectedRange); |
| |
| NSRange result = selectedRange; |
| if (result.location == NSNotFound) |
| LOG(TextInput, "selectedRange -> (NSNotFound, %u)", result.length); |
| else |
| LOG(TextInput, "selectedRange -> (%u, %u)", result.location, result.length); |
| |
| return result; |
| } |
| |
| - (BOOL)hasMarkedText |
| { |
| WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters; |
| |
| BOOL result; |
| if (parameters) { |
| result = _data->_page->editorState().hasComposition; |
| if (result) { |
| // A saved command can confirm a composition, but it cannot start a new one. |
| [self _executeSavedKeypressCommands]; |
| result = _data->_page->editorState().hasComposition; |
| } |
| } else { |
| EditingRange markedRange; |
| _data->_page->getMarkedRange(markedRange); |
| result = markedRange.location != notFound; |
| } |
| |
| LOG(TextInput, "hasMarkedText -> %u", result); |
| return result; |
| } |
| |
| - (void)unmarkText |
| { |
| [self _executeSavedKeypressCommands]; |
| |
| LOG(TextInput, "unmarkText"); |
| |
| // Use pointer to get parameters passed to us by the caller of interpretKeyEvents. |
| WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters; |
| |
| if (parameters) { |
| parameters->eventInterpretationHadSideEffects = true; |
| parameters->consumedByIM = false; |
| } |
| |
| _data->_page->confirmComposition(); |
| } |
| |
| - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelectedRange replacementRange:(NSRange)replacementRange |
| { |
| [self _executeSavedKeypressCommands]; |
| |
| BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; |
| ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]); |
| |
| LOG(TextInput, "setMarkedText:\"%@\" selectedRange:(%u, %u)", isAttributedString ? [string string] : string, newSelectedRange.location, newSelectedRange.length); |
| |
| // Use pointer to get parameters passed to us by the caller of interpretKeyEvents. |
| WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters; |
| |
| if (parameters) { |
| parameters->eventInterpretationHadSideEffects = true; |
| parameters->consumedByIM = false; |
| } |
| |
| Vector<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]; |
| extractUnderlines(string, underlines); |
| } else |
| text = string; |
| |
| if (_data->_page->editorState().isInPasswordField) { |
| // 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(!_data->_page->editorState().hasComposition); |
| _data->_impl->notifyInputContextAboutDiscardedComposition(); |
| if ([text length] == 1 && [[text decomposedStringWithCanonicalMapping] characterAtIndex:0] < 0x80) { |
| _data->_page->insertText(text, replacementRange); |
| } else |
| NSBeep(); |
| return; |
| } |
| |
| _data->_page->setComposition(text, underlines, newSelectedRange, replacementRange); |
| } |
| |
| - (NSRange)markedRange |
| { |
| [self _executeSavedKeypressCommands]; |
| |
| EditingRange markedRange; |
| _data->_page->getMarkedRange(markedRange); |
| |
| NSRange result = markedRange; |
| if (result.location == NSNotFound) |
| LOG(TextInput, "markedRange -> (NSNotFound, %u)", result.length); |
| else |
| LOG(TextInput, "markedRange -> (%u, %u)", result.location, result.length); |
| |
| return result; |
| } |
| |
| - (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)nsRange actualRange:(NSRangePointer)actualRange |
| { |
| [self _executeSavedKeypressCommands]; |
| |
| if (!_data->_page->editorState().isContentEditable) { |
| LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> nil", nsRange.location, nsRange.length); |
| return nil; |
| } |
| |
| if (_data->_page->editorState().isInPasswordField) |
| return nil; |
| |
| AttributedString result; |
| _data->_page->getAttributedSubstringFromRange(nsRange, result); |
| |
| if (actualRange) { |
| *actualRange = nsRange; |
| actualRange->length = [result.string length]; |
| } |
| |
| LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> \"%@\"", nsRange.location, nsRange.length, [result.string string]); |
| return [[result.string retain] autorelease]; |
| } |
| |
| - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint |
| { |
| [self _executeSavedKeypressCommands]; |
| |
| NSWindow *window = [self window]; |
| |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Wdeprecated-declarations" |
| if (window) |
| thePoint = [window convertScreenToBase:thePoint]; |
| #pragma clang diagnostic pop |
| thePoint = [self convertPoint:thePoint fromView:nil]; // the point is relative to the main frame |
| |
| uint64_t result = _data->_page->characterIndexForPoint(IntPoint(thePoint)); |
| if (result == notFound) |
| result = NSNotFound; |
| LOG(TextInput, "characterIndexForPoint:(%f, %f) -> %u", thePoint.x, thePoint.y, result); |
| return result; |
| } |
| |
| - (NSRect)firstRectForCharacterRange:(NSRange)theRange actualRange:(NSRangePointer)actualRange |
| { |
| [self _executeSavedKeypressCommands]; |
| |
| // 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 ((theRange.location + theRange.length < theRange.location) && (theRange.location + theRange.length != 0)) |
| theRange.length = 0; |
| |
| if (theRange.location == NSNotFound) { |
| if (actualRange) |
| *actualRange = theRange; |
| LOG(TextInput, "firstRectForCharacterRange:(NSNotFound, %u) -> NSZeroRect", theRange.length); |
| return NSZeroRect; |
| } |
| |
| NSRect resultRect = _data->_page->firstRectForCharacterRange(theRange); |
| resultRect = [self convertRect:resultRect toView:nil]; |
| resultRect = [self.window convertRectToScreen:resultRect]; |
| |
| if (actualRange) { |
| // FIXME: Update actualRange to match the range of first rect. |
| *actualRange = theRange; |
| } |
| |
| LOG(TextInput, "firstRectForCharacterRange:(%u, %u) -> (%f, %f, %f, %f)", theRange.location, theRange.length, resultRect.origin.x, resultRect.origin.y, resultRect.size.width, resultRect.size.height); |
| return resultRect; |
| } |
| |
| - (BOOL)performKeyEquivalent:(NSEvent *)event |
| { |
| if (_data->_impl->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. |
| [[event retain] autorelease]; |
| |
| // 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] & NSDeviceIndependentModifierFlagsMask)) |
| return [super performKeyEquivalent:event]; |
| |
| if (_data->_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 [super performKeyEquivalent:event]; |
| } |
| |
| ASSERT(event == [NSApp currentEvent]); |
| |
| _data->_impl->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 (self == [[self window] firstResponder]) { |
| Vector<KeypressCommand> commands; |
| BOOL handledByInputMethod = [self _interpretKeyEvent:event savingCommandsTo:commands]; |
| _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(event, handledByInputMethod, commands)); |
| return YES; |
| } |
| |
| return [super performKeyEquivalent:event]; |
| } |
| |
| - (void)keyUp:(NSEvent *)theEvent |
| { |
| if (_data->_impl->ignoresNonWheelEvents()) |
| return; |
| |
| LOG(TextInput, "keyUp:%p %@", theEvent, theEvent); |
| // We don't interpret the keyUp event, as this breaks key bindings (see <https://bugs.webkit.org/show_bug.cgi?id=130100>). |
| _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent, false, Vector<KeypressCommand>())); |
| } |
| |
| - (void)keyDown:(NSEvent *)theEvent |
| { |
| if (_data->_impl->ignoresNonWheelEvents()) |
| return; |
| |
| LOG(TextInput, "keyDown:%p %@%s", theEvent, theEvent, (theEvent == _data->_keyDownEventBeingResent) ? " (re-sent)" : ""); |
| |
| // 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. |
| [[theEvent retain] autorelease]; |
| |
| if (_data->_impl->tryHandlePluginComplexTextInputKeyDown(theEvent)) { |
| 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 (_data->_keyDownEventBeingResent == theEvent) { |
| [super keyDown:theEvent]; |
| return; |
| } |
| |
| Vector<KeypressCommand> commands; |
| BOOL handledByInputMethod = [self _interpretKeyEvent:theEvent savingCommandsTo:commands]; |
| if (!commands.isEmpty()) { |
| // An input method may make several actions per keypress. For example, pressing Return with Korean IM both confirms it and sends a newline. |
| // IM-like actions are handled immediately (so the return value from UI process is true), but there are saved commands that |
| // should be handled like normal text input after DOM event dispatch. |
| handledByInputMethod = NO; |
| } |
| |
| _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent, handledByInputMethod, commands)); |
| } |
| |
| - (void)flagsChanged:(NSEvent *)theEvent |
| { |
| if (_data->_impl->ignoresNonWheelEvents()) |
| return; |
| |
| LOG(TextInput, "flagsChanged:%p %@", theEvent, theEvent); |
| |
| // 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. |
| [[theEvent retain] autorelease]; |
| |
| unsigned short keyCode = [theEvent keyCode]; |
| |
| // Don't make an event from the num lock and function keys |
| if (!keyCode || keyCode == 10 || keyCode == 63) |
| return; |
| |
| _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent, false, Vector<KeypressCommand>())); |
| } |
| |
| #endif // USE(ASYNC_NSTEXTINPUTCLIENT) |
| |
| - (NSTextInputContext *)_superInputContext |
| { |
| return [super inputContext]; |
| } |
| |
| - (void)_superQuickLookWithEvent:(NSEvent *)event |
| { |
| [super quickLookWithEvent:event]; |
| } |
| |
| - (void)_superSwipeWithEvent:(NSEvent *)event |
| { |
| [super swipeWithEvent:event]; |
| } |
| |
| - (void)_superMagnifyWithEvent:(NSEvent *)event |
| { |
| [super magnifyWithEvent:event]; |
| } |
| |
| - (void)_superSmartMagnifyWithEvent:(NSEvent *)event |
| { |
| [super smartMagnifyWithEvent:event]; |
| } |
| |
| - (void)_superRemoveTrackingRect:(NSTrackingRectTag)tag |
| { |
| [super removeTrackingRect:tag]; |
| } |
| |
| - (NSArray *)validAttributesForMarkedText |
| { |
| static NSArray *validAttributes; |
| if (!validAttributes) { |
| validAttributes = [[NSArray alloc] initWithObjects: |
| NSUnderlineStyleAttributeName, NSUnderlineColorAttributeName, |
| NSMarkedClauseSegmentAttributeName, |
| #if USE(DICTATION_ALTERNATIVES) |
| NSTextAlternativesAttributeName, |
| #endif |
| #if USE(INSERTION_UNDO_GROUPING) |
| NSTextInsertionUndoableAttributeName, |
| #endif |
| nil]; |
| // 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; |
| } |
| |
| #if ENABLE(DRAG_SUPPORT) |
| - (void)draggedImage:(NSImage *)image endedAt:(NSPoint)endPoint operation:(NSDragOperation)operation |
| { |
| _data->_impl->draggedImage(image, NSPointToCGPoint(endPoint), operation); |
| } |
| |
| - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)draggingInfo |
| { |
| return _data->_impl->draggingEntered(draggingInfo); |
| } |
| |
| - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)draggingInfo |
| { |
| return _data->_impl->draggingUpdated(draggingInfo); |
| } |
| |
| - (void)draggingExited:(id <NSDraggingInfo>)draggingInfo |
| { |
| _data->_impl->draggingExited(draggingInfo); |
| } |
| |
| - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)draggingInfo |
| { |
| return _data->_impl->prepareForDragOperation(draggingInfo); |
| } |
| |
| - (BOOL)performDragOperation:(id <NSDraggingInfo>)draggingInfo |
| { |
| return _data->_impl->performDragOperation(draggingInfo); |
| } |
| |
| - (NSView *)_hitTest:(NSPoint *)point dragTypes:(NSSet *)types |
| { |
| return _data->_impl->hitTestForDragTypes(NSPointToCGPoint(*point), types); |
| } |
| #endif // ENABLE(DRAG_SUPPORT) |
| |
| - (BOOL)_windowResizeMouseLocationIsInVisibleScrollerThumb:(NSPoint)loc |
| { |
| NSPoint localPoint = [self convertPoint:loc fromView:nil]; |
| NSRect visibleThumbRect = NSRect(_data->_page->visibleScrollerThumbRect()); |
| return NSMouseInRect(localPoint, visibleThumbRect, [self isFlipped]); |
| } |
| |
| - (void)_addFontPanelObserver |
| { |
| _data->_impl->startObservingFontPanel(); |
| } |
| |
| - (void)viewWillMoveToWindow:(NSWindow *)window |
| { |
| _data->_impl->viewWillMoveToWindow(window); |
| } |
| |
| - (void)viewDidMoveToWindow |
| { |
| _data->_impl->viewDidMoveToWindow(); |
| } |
| |
| - (void)drawRect:(NSRect)rect |
| { |
| LOG(Printing, "drawRect: x:%g, y:%g, width:%g, height:%g", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); |
| _data->_page->endPrinting(); |
| } |
| |
| - (BOOL)isOpaque |
| { |
| return _data->_page->drawsBackground(); |
| } |
| |
| - (BOOL)mouseDownCanMoveWindow |
| { |
| // -[NSView mouseDownCanMoveWindow] returns YES when the NSView is transparent, |
| // but we don't want a drag in the NSView to move the window, even if it's transparent. |
| return NO; |
| } |
| |
| - (void)viewDidHide |
| { |
| _data->_page->viewStateDidChange(ViewState::IsVisible); |
| } |
| |
| - (void)viewDidUnhide |
| { |
| _data->_page->viewStateDidChange(ViewState::IsVisible); |
| } |
| |
| - (void)viewDidChangeBackingProperties |
| { |
| _data->_impl->viewDidChangeBackingProperties(); |
| } |
| |
| - (void)_activeSpaceDidChange:(NSNotification *)notification |
| { |
| _data->_page->viewStateDidChange(ViewState::IsVisible); |
| } |
| |
| - (void)_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 = _data->_page->process().processIdentifier(); |
| else if (!registerProcess) { |
| pid = WKAXRemoteProcessIdentifier(_data->_remoteAccessibilityChild.get()); |
| _data->_remoteAccessibilityChild = nil; |
| } |
| if (pid) |
| WKAXRegisterRemoteProcess(registerProcess, pid); |
| } |
| |
| - (void)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(); |
| _data->_impl->updateWindowAndViewFrames(); |
| } |
| |
| - (id)accessibilityFocusedUIElement |
| { |
| [self enableAccessibilityIfNecessary]; |
| return _data->_remoteAccessibilityChild.get(); |
| } |
| |
| - (BOOL)accessibilityIsIgnored |
| { |
| return NO; |
| } |
| |
| - (id)accessibilityHitTest:(NSPoint)point |
| { |
| [self enableAccessibilityIfNecessary]; |
| return _data->_remoteAccessibilityChild.get(); |
| } |
| |
| - (id)accessibilityAttributeValue:(NSString*)attribute |
| { |
| [self enableAccessibilityIfNecessary]; |
| |
| if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) { |
| |
| id child = nil; |
| if (_data->_remoteAccessibilityChild) |
| child = _data->_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([self superview]); |
| if ([attribute isEqualToString:NSAccessibilityEnabledAttribute]) |
| return [NSNumber numberWithBool:YES]; |
| |
| return [super accessibilityAttributeValue:attribute]; |
| } |
| |
| - (NSView *)hitTest:(NSPoint)point |
| { |
| NSView *hitView = [super hitTest:point]; |
| if (hitView && _data && hitView == _data->_impl->layerHostingView()) |
| hitView = self; |
| |
| return hitView; |
| } |
| |
| - (NSInteger)conversationIdentifier |
| { |
| return (NSInteger)self; |
| } |
| |
| - (void)quickLookWithEvent:(NSEvent *)event |
| { |
| _data->_impl->quickLookWithEvent(event); |
| } |
| |
| - (std::unique_ptr<WebKit::DrawingAreaProxy>)_createDrawingAreaProxy |
| { |
| if ([[[NSUserDefaults standardUserDefaults] objectForKey:@"WebKit2UseRemoteLayerTreeDrawingArea"] boolValue]) |
| return std::make_unique<RemoteLayerTreeDrawingAreaProxy>(*_data->_page); |
| |
| return std::make_unique<TiledCoreAnimationDrawingAreaProxy>(*_data->_page); |
| } |
| |
| - (WebKit::ColorSpaceData)_colorSpace |
| { |
| return _data->_impl->colorSpace(); |
| } |
| |
| - (void)_processDidExit |
| { |
| _data->_impl->notifyInputContextAboutDiscardedComposition(); |
| |
| if (_data->_impl->layerHostingView()) |
| _data->_impl->setAcceleratedCompositingRootLayer(nil); |
| |
| [self _updateRemoteAccessibilityRegistration:NO]; |
| |
| _data->_impl->resetGestureController(); |
| } |
| |
| - (void)_pageClosed |
| { |
| [self _updateRemoteAccessibilityRegistration:NO]; |
| } |
| |
| - (void)_didRelaunchProcess |
| { |
| _data->_impl->accessibilityRegisterUIProcessTokens(); |
| } |
| |
| - (void)_setUserInterfaceItemState:(NSString *)commandName enabled:(BOOL)isEnabled state:(int)newState |
| { |
| ValidationVector items = _data->_validationMap.take(commandName); |
| size_t size = items.size(); |
| for (size_t i = 0; i < size; ++i) { |
| ValidationItem item = items[i].get(); |
| [menuItem(item) setState:newState]; |
| [menuItem(item) setEnabled:isEnabled]; |
| [toolbarItem(item) setEnabled:isEnabled]; |
| // FIXME <rdar://problem/8803392>: If the item is neither a menu nor toolbar item, it will be left enabled. |
| } |
| } |
| |
| - (void)_doneWithKeyEvent:(NSEvent *)event eventWasHandled:(BOOL)eventWasHandled |
| { |
| if ([event type] != NSKeyDown) |
| return; |
| |
| if (_data->_impl->tryPostProcessPluginComplexTextInputKeyDown(event)) |
| return; |
| |
| if (eventWasHandled) { |
| [NSCursor setHiddenUntilMouseMoves:YES]; |
| return; |
| } |
| |
| // resending the event may destroy this WKView |
| RetainPtr<WKView> protector(self); |
| |
| ASSERT(!_data->_keyDownEventBeingResent); |
| _data->_keyDownEventBeingResent = event; |
| [NSApp _setCurrentEvent:event]; |
| [NSApp sendEvent:event]; |
| |
| _data->_keyDownEventBeingResent = nullptr; |
| } |
| |
| - (NSRect)_convertToDeviceSpace:(NSRect)rect |
| { |
| return toDeviceSpace(rect, [self window]); |
| } |
| |
| - (NSRect)_convertToUserSpace:(NSRect)rect |
| { |
| return toUserSpace(rect, [self window]); |
| } |
| |
| - (NSTrackingRectTag)addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside |
| { |
| return _data->_impl->addTrackingRect(NSRectToCGRect(rect), owner, data, assumeInside); |
| } |
| |
| - (NSTrackingRectTag)_addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside useTrackingNum:(int)tag |
| { |
| return _data->_impl->addTrackingRectWithTrackingNum(NSRectToCGRect(rect), owner, data, assumeInside, tag); |
| } |
| |
| - (void)_addTrackingRects:(NSRect *)rects owner:(id)owner userDataList:(void **)userDataList assumeInsideList:(BOOL *)assumeInsideList trackingNums:(NSTrackingRectTag *)trackingNums count:(int)count |
| { |
| CGRect *cgRects = (CGRect *)calloc(1, sizeof(CGRect)); |
| for (int i = 0; i < count; i++) |
| cgRects[i] = NSRectToCGRect(rects[i]); |
| _data->_impl->addTrackingRectsWithTrackingNums(cgRects, owner, userDataList, assumeInsideList, trackingNums, count); |
| free(cgRects); |
| } |
| |
| - (void)removeTrackingRect:(NSTrackingRectTag)tag |
| { |
| if (!_data) |
| return; |
| _data->_impl->removeTrackingRect(tag); |
| } |
| |
| - (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count |
| { |
| if (!_data) |
| return; |
| _data->_impl->removeTrackingRects(tags, count); |
| } |
| |
| - (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)data |
| { |
| return _data->_impl->stringForToolTip(tag); |
| } |
| |
| - (void)_toolTipChangedFrom:(NSString *)oldToolTip to:(NSString *)newToolTip |
| { |
| _data->_impl->toolTipChanged(oldToolTip, newToolTip); |
| } |
| |
| - (void)_setAccessibilityWebProcessToken:(NSData *)data |
| { |
| _data->_remoteAccessibilityChild = WKAXRemoteElementForToken(data); |
| [self _updateRemoteAccessibilityRegistration:YES]; |
| } |
| |
| - (void)_dragImageForView:(NSView *)view withImage:(NSImage *)image at:(NSPoint)clientPoint linkDrag:(BOOL)linkDrag |
| { |
| // The call below could release this WKView. |
| RetainPtr<WKView> protector(self); |
| |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Wdeprecated-declarations" |
| [view dragImage:image |
| at:clientPoint |
| offset:NSZeroSize |
| event:(linkDrag) ? [NSApp currentEvent] : _data->_mouseDownEvent |
| pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard] |
| source:self |
| slideBack:YES]; |
| #pragma clang diagnostic pop |
| } |
| |
| static bool matchesExtensionOrEquivalent(NSString *filename, NSString *extension) |
| { |
| NSString *extensionAsSuffix = [@"." stringByAppendingString:extension]; |
| return hasCaseInsensitiveSuffix(filename, extensionAsSuffix) || (stringIsCaseInsensitiveEqualToString(extension, @"jpeg") |
| && hasCaseInsensitiveSuffix(filename, @".jpg")); |
| } |
| |
| - (void)_setFileAndURLTypes:(NSString *)filename withExtension:(NSString *)extension withTitle:(NSString *)title withURL:(NSString *)url withVisibleURL:(NSString *)visibleUrl forPasteboard:(NSPasteboard *)pasteboard |
| { |
| if (!matchesExtensionOrEquivalent(filename, extension)) |
| filename = [[filename stringByAppendingString:@"."] stringByAppendingString:extension]; |
| |
| [pasteboard setString:visibleUrl forType:NSStringPboardType]; |
| [pasteboard setString:visibleUrl forType:PasteboardTypes::WebURLPboardType]; |
| [pasteboard setString:title forType:PasteboardTypes::WebURLNamePboardType]; |
| [pasteboard setPropertyList:[NSArray arrayWithObjects:[NSArray arrayWithObject:visibleUrl], [NSArray arrayWithObject:title], nil] forType:PasteboardTypes::WebURLsWithTitlesPboardType]; |
| [pasteboard setPropertyList:[NSArray arrayWithObject:extension] forType:NSFilesPromisePboardType]; |
| _data->_promisedFilename = filename; |
| _data->_promisedURL = url; |
| } |
| |
| - (void)_setPromisedDataForImage:(WebCore::Image *)image withFileName:(NSString *)filename withExtension:(NSString *)extension withTitle:(NSString *)title withURL:(NSString *)url withVisibleURL:(NSString *)visibleUrl withArchive:(WebCore::SharedBuffer*) archiveBuffer forPasteboard:(NSString *)pasteboardName |
| |
| { |
| NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:pasteboardName]; |
| RetainPtr<NSMutableArray> types = adoptNS([[NSMutableArray alloc] initWithObjects:NSFilesPromisePboardType, nil]); |
| |
| [types addObjectsFromArray:archiveBuffer ? PasteboardTypes::forImagesWithArchive() : PasteboardTypes::forImages()]; |
| [pasteboard declareTypes:types.get() owner:self]; |
| [self _setFileAndURLTypes:filename withExtension:extension withTitle:title withURL:url withVisibleURL:visibleUrl forPasteboard:pasteboard]; |
| |
| if (archiveBuffer) |
| [pasteboard setData:archiveBuffer->createNSData().get() forType:PasteboardTypes::WebArchivePboardType]; |
| |
| _data->_promisedImage = image; |
| } |
| |
| #if ENABLE(ATTACHMENT_ELEMENT) |
| - (void)_setPromisedDataForAttachment:(NSString *)filename withExtension:(NSString *)extension withTitle:(NSString *)title withURL:(NSString *)url withVisibleURL:(NSString *)visibleUrl forPasteboard:(NSString *)pasteboardName |
| |
| { |
| NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:pasteboardName]; |
| RetainPtr<NSMutableArray> types = adoptNS([[NSMutableArray alloc] initWithObjects:NSFilesPromisePboardType, nil]); |
| [types addObjectsFromArray:PasteboardTypes::forURL()]; |
| [pasteboard declareTypes:types.get() owner:self]; |
| [self _setFileAndURLTypes:filename withExtension:extension withTitle:title withURL:url withVisibleURL:visibleUrl forPasteboard:pasteboard]; |
| |
| RetainPtr<NSMutableArray> paths = adoptNS([[NSMutableArray alloc] init]); |
| [paths addObject:title]; |
| [pasteboard setPropertyList:paths.get() forType:NSFilenamesPboardType]; |
| |
| _data->_promisedImage = nullptr; |
| } |
| #endif |
| |
| - (void)pasteboardChangedOwner:(NSPasteboard *)pasteboard |
| { |
| _data->_promisedImage = nullptr; |
| _data->_promisedFilename = ""; |
| _data->_promisedURL = ""; |
| } |
| |
| - (void)pasteboard:(NSPasteboard *)pasteboard provideDataForType:(NSString *)type |
| { |
| // FIXME: need to support NSRTFDPboardType |
| |
| if ([type isEqual:NSTIFFPboardType] && _data->_promisedImage) { |
| [pasteboard setData:(NSData *)_data->_promisedImage->getTIFFRepresentation() forType:NSTIFFPboardType]; |
| _data->_promisedImage = nullptr; |
| } |
| } |
| |
| 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 *)namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination |
| { |
| RetainPtr<NSFileWrapper> wrapper; |
| RetainPtr<NSData> data; |
| |
| if (_data->_promisedImage) { |
| data = _data->_promisedImage->data()->createNSData(); |
| wrapper = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:data.get()]); |
| } else |
| wrapper = adoptNS([[NSFileWrapper alloc] initWithURL:[NSURL URLWithString:_data->_promisedURL] options:NSFileWrapperReadingImmediate error:nil]); |
| |
| if (wrapper) |
| [wrapper setPreferredFilename:_data->_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] options:NSFileWrapperWritingWithNameUpdating originalContentsURL:nil error:nullptr]) |
| LOG_ERROR("Failed to create image file via -[NSFileWrapper writeToURL:options:originalContentsURL:error:]"); |
| |
| if (!_data->_promisedURL.isEmpty()) |
| WebCore::setMetadataURL(_data->_promisedURL, "", String(path)); |
| |
| return [NSArray arrayWithObject:[path lastPathComponent]]; |
| } |
| |
| #if ENABLE(FULLSCREEN_API) |
| - (BOOL)_hasFullScreenWindowController |
| { |
| return _data->_impl->hasFullScreenWindowController(); |
| } |
| |
| - (WKFullScreenWindowController *)_fullScreenWindowController |
| { |
| return _data->_impl->fullScreenWindowController(); |
| } |
| |
| - (void)_closeFullScreenWindowController |
| { |
| _data->_impl->closeFullScreenWindowController(); |
| } |
| #endif |
| |
| - (bool)_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:self]); |
| [super doCommandBySelector:selector]; |
| [sink detach]; |
| return ![sink didReceiveUnhandledCommand]; |
| } |
| |
| - (NSInteger)spellCheckerDocumentTag |
| { |
| if (!_data->_hasSpellCheckerDocumentTag) { |
| _data->_spellCheckerDocumentTag = [NSSpellChecker uniqueSpellDocumentTag]; |
| _data->_hasSpellCheckerDocumentTag = YES; |
| } |
| return _data->_spellCheckerDocumentTag; |
| } |
| |
| - (void)handleAcceptedAlternativeText:(NSString*)text |
| { |
| _data->_page->handleAlternativeTextUIResult(text); |
| } |
| |
| - (void)_setSuppressVisibilityUpdates:(BOOL)suppressVisibilityUpdates |
| { |
| _data->_page->setSuppressVisibilityUpdates(suppressVisibilityUpdates); |
| } |
| |
| - (BOOL)_suppressVisibilityUpdates |
| { |
| return _data->_page->suppressVisibilityUpdates(); |
| } |
| |
| - (NSTrackingArea *)_primaryTrackingArea |
| { |
| return _data->_primaryTrackingArea.get(); |
| } |
| |
| - (void)_setPrimaryTrackingArea:(NSTrackingArea *)trackingArea |
| { |
| [self removeTrackingArea:_data->_primaryTrackingArea.get()]; |
| _data->_primaryTrackingArea = trackingArea; |
| [self addTrackingArea:trackingArea]; |
| } |
| |
| - (instancetype)initWithFrame:(NSRect)frame processPool:(WebProcessPool&)processPool configuration:(Ref<API::PageConfiguration>&&)configuration webView:(WKWebView *)webView |
| { |
| self = [super initWithFrame:frame]; |
| if (!self) |
| return nil; |
| |
| [NSApp registerServicesMenuSendTypes:PasteboardTypes::forSelection() returnTypes:PasteboardTypes::forEditing()]; |
| |
| InitializeWebKit2(); |
| |
| // Legacy style scrollbars have design details that rely on tracking the mouse all the time. |
| NSTrackingAreaOptions options = NSTrackingMouseMoved | NSTrackingMouseEnteredAndExited | NSTrackingInVisibleRect | NSTrackingCursorUpdate; |
| if (WKRecommendedScrollerStyle() == NSScrollerStyleLegacy) |
| options |= NSTrackingActiveAlways; |
| else |
| options |= NSTrackingActiveInKeyWindow; |
| |
| _data = [[WKViewData alloc] init]; |
| _data->_primaryTrackingArea = adoptNS([[NSTrackingArea alloc] initWithRect:frame options:options owner:self userInfo:nil]); |
| [self addTrackingArea:_data->_primaryTrackingArea.get()]; |
| |
| _data->_pageClient = std::make_unique<PageClientImpl>(self, webView); |
| _data->_page = processPool.createWebPage(*_data->_pageClient, WTF::move(configuration)); |
| |
| _data->_impl = std::make_unique<WebViewImpl>(self, *_data->_page, *_data->_pageClient); |
| static_cast<PageClientImpl&>(*_data->_pageClient).setImpl(*_data->_impl); |
| |
| _data->_page->setAddsVisitedLinks(processPool.historyClient().addsVisitedLinks()); |
| |
| _data->_page->initializeWebPage(); |
| |
| _data->_mouseDownEvent = nil; |
| _data->_windowOcclusionDetectionEnabled = YES; |
| |
| _data->_impl->registerDraggedTypes(); |
| |
| self.wantsLayer = YES; |
| |
| // Explicitly set the layer contents placement so AppKit will make sure that our layer has masksToBounds set to YES. |
| self.layerContentsPlacement = NSViewLayerContentsPlacementTopLeft; |
| |
| WebProcessPool::statistics().wkViewCount++; |
| |
| NSNotificationCenter* workspaceNotificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter]; |
| [workspaceNotificationCenter addObserver:self selector:@selector(_activeSpaceDidChange:) name:NSWorkspaceActiveSpaceDidChangeNotification object:nil]; |
| |
| return self; |
| } |
| |
| #if WK_API_ENABLED |
| - (void)_setThumbnailView:(_WKThumbnailView *)thumbnailView |
| { |
| _data->_impl->setThumbnailView(thumbnailView); |
| } |
| |
| - (_WKThumbnailView *)_thumbnailView |
| { |
| if (!_data->_impl) |
| return nil; |
| return _data->_impl->thumbnailView(); |
| } |
| #endif // WK_API_ENABLED |
| |
| #if WK_API_ENABLED |
| - (_WKRemoteObjectRegistry *)_remoteObjectRegistry |
| { |
| if (!_data->_remoteObjectRegistry) { |
| _data->_remoteObjectRegistry = adoptNS([[_WKRemoteObjectRegistry alloc] _initWithMessageSender:*_data->_page]); |
| _data->_page->process().processPool().addMessageReceiver(Messages::RemoteObjectRegistry::messageReceiverName(), _data->_page->pageID(), [_data->_remoteObjectRegistry remoteObjectRegistry]); |
| } |
| |
| return _data->_remoteObjectRegistry.get(); |
| } |
| #endif |
| |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100 |
| - (void)_startWindowDrag |
| { |
| [[self window] performWindowDragWithEvent:_data->_mouseDownEvent]; |
| } |
| #endif |
| |
| // FIXME: Get rid of this when we have better plumbing to WKViewLayoutStrategy. |
| - (void)_updateViewExposedRect |
| { |
| _data->_impl->updateViewExposedRect(); |
| } |
| |
| @end |
| |
| @implementation WKView (Private) |
| |
| - (void)saveBackForwardSnapshotForCurrentItem |
| { |
| _data->_page->recordNavigationSnapshot(); |
| } |
| |
| - (void)saveBackForwardSnapshotForItem:(WKBackForwardListItemRef)item |
| { |
| _data->_page->recordNavigationSnapshot(*toImpl(item)); |
| } |
| |
| - (id)initWithFrame:(NSRect)frame contextRef:(WKContextRef)contextRef pageGroupRef:(WKPageGroupRef)pageGroupRef |
| { |
| return [self initWithFrame:frame contextRef:contextRef pageGroupRef:pageGroupRef relatedToPage:nil]; |
| } |
| |
| - (id)initWithFrame:(NSRect)frame contextRef:(WKContextRef)contextRef pageGroupRef:(WKPageGroupRef)pageGroupRef relatedToPage:(WKPageRef)relatedPage |
| { |
| auto configuration = API::PageConfiguration::create(); |
| configuration->setProcessPool(toImpl(contextRef)); |
| configuration->setPageGroup(toImpl(pageGroupRef)); |
| configuration->setRelatedPage(toImpl(relatedPage)); |
| |
| return [self initWithFrame:frame processPool:*toImpl(contextRef) configuration:WTF::move(configuration) webView:nil]; |
| } |
| |
| - (id)initWithFrame:(NSRect)frame configurationRef:(WKPageConfigurationRef)configurationRef |
| { |
| Ref<API::PageConfiguration> configuration = toImpl(configurationRef)->copy(); |
| auto& processPool = *configuration->processPool(); |
| |
| return [self initWithFrame:frame processPool:processPool configuration:WTF::move(configuration) webView:nil]; |
| } |
| |
| - (BOOL)wantsUpdateLayer |
| { |
| return YES; |
| } |
| |
| - (void)updateLayer |
| { |
| if ([self drawsBackground] && ![self drawsTransparentBackground]) |
| self.layer.backgroundColor = CGColorGetConstantColor(kCGColorWhite); |
| else |
| self.layer.backgroundColor = CGColorGetConstantColor(kCGColorClear); |
| |
| // If asynchronous geometry updates have been sent by forceAsyncDrawingAreaSizeUpdate, |
| // then subsequent calls to setFrameSize should not result in us waiting for the did |
| // udpate response if setFrameSize is called. |
| if ([self frameSizeUpdatesDisabled]) |
| return; |
| |
| if (DrawingAreaProxy* drawingArea = _data->_page->drawingArea()) |
| drawingArea->waitForPossibleGeometryUpdate(); |
| } |
| |
| - (WKPageRef)pageRef |
| { |
| return toAPI(_data->_page.get()); |
| } |
| |
| - (BOOL)canChangeFrameLayout:(WKFrameRef)frameRef |
| { |
| // PDF documents are already paginated, so we can't change them to add headers and footers. |
| return !toImpl(frameRef)->isDisplayingPDFDocument(); |
| } |
| |
| - (NSPrintOperation *)printOperationWithPrintInfo:(NSPrintInfo *)printInfo forFrame:(WKFrameRef)frameRef |
| { |
| LOG(Printing, "Creating an NSPrintOperation for frame '%s'", toImpl(frameRef)->url().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:toImpl(frameRef) view:self]); |
| // NSPrintOperation takes ownership of the view. |
| NSPrintOperation *printOperation = [NSPrintOperation printOperationWithView:printingView.get() printInfo:printInfo]; |
| [printOperation setCanSpawnSeparateThread:YES]; |
| [printOperation setJobTitle:toImpl(frameRef)->title()]; |
| printingView->_printOperation = printOperation; |
| return printOperation; |
| } |
| |
| - (void)setFrame:(NSRect)rect andScrollBy:(NSSize)offset |
| { |
| _data->_impl->setFrameAndScrollBy(NSRectToCGRect(rect), NSSizeToCGSize(offset)); |
| } |
| |
| - (void)disableFrameSizeUpdates |
| { |
| _data->_impl->disableFrameSizeUpdates(); |
| } |
| |
| - (void)enableFrameSizeUpdates |
| { |
| _data->_impl->enableFrameSizeUpdates(); |
| } |
| |
| - (BOOL)frameSizeUpdatesDisabled |
| { |
| return _data->_impl->frameSizeUpdatesDisabled(); |
| } |
| |
| + (void)hideWordDefinitionWindow |
| { |
| DictionaryLookup::hidePopup(); |
| } |
| |
| - (NSSize)minimumSizeForAutoLayout |
| { |
| return _data->_page->minimumLayoutSize(); |
| } |
| |
| - (void)setMinimumSizeForAutoLayout:(NSSize)minimumSizeForAutoLayout |
| { |
| BOOL expandsToFit = minimumSizeForAutoLayout.width > 0; |
| |
| _data->_page->setMinimumLayoutSize(IntSize(minimumSizeForAutoLayout.width, minimumSizeForAutoLayout.height)); |
| _data->_page->setMainFrameIsScrollable(!expandsToFit); |
| |
| [self setShouldClipToVisibleRect:expandsToFit]; |
| } |
| |
| - (BOOL)shouldExpandToViewHeightForAutoLayout |
| { |
| return _data->_page->autoSizingShouldExpandToViewHeight(); |
| } |
| |
| - (void)setShouldExpandToViewHeightForAutoLayout:(BOOL)shouldExpand |
| { |
| return _data->_page->setAutoSizingShouldExpandToViewHeight(shouldExpand); |
| } |
| |
| - (BOOL)shouldClipToVisibleRect |
| { |
| return _data->_impl->clipsToVisibleRect(); |
| } |
| |
| - (void)setShouldClipToVisibleRect:(BOOL)clipsToVisibleRect |
| { |
| _data->_impl->setClipsToVisibleRect(clipsToVisibleRect); |
| } |
| |
| - (NSColor *)underlayColor |
| { |
| Color webColor = _data->_page->underlayColor(); |
| if (!webColor.isValid()) |
| return nil; |
| |
| return nsColor(webColor); |
| } |
| |
| - (void)setUnderlayColor:(NSColor *)underlayColor |
| { |
| _data->_page->setUnderlayColor(colorFromNSColor(underlayColor)); |
| } |
| |
| #if WK_API_ENABLED |
| - (NSView *)_inspectorAttachmentView |
| { |
| NSView *attachmentView = _data->_inspectorAttachmentView.get(); |
| return attachmentView ? attachmentView : self; |
| } |
| |
| - (void)_setInspectorAttachmentView:(NSView *)newView |
| { |
| NSView *oldView = _data->_inspectorAttachmentView.get(); |
| if (oldView == newView) |
| return; |
| |
| _data->_inspectorAttachmentView = newView; |
| _data->_page->inspector()->attachmentViewDidChange(oldView ? oldView : self, newView ? newView : self); |
| } |
| #endif |
| |
| - (NSView *)fullScreenPlaceholderView |
| { |
| return _data->_impl->fullScreenPlaceholderView(); |
| } |
| |
| // FIXME: This returns an autoreleased object. Should it really be prefixed 'create'? |
| - (NSWindow *)createFullScreenWindow |
| { |
| return _data->_impl->createFullScreenWindow(); |
| } |
| |
| - (void)beginDeferringViewInWindowChanges |
| { |
| _data->_impl->beginDeferringViewInWindowChanges(); |
| } |
| |
| - (void)endDeferringViewInWindowChanges |
| { |
| _data->_impl->endDeferringViewInWindowChanges(); |
| } |
| |
| - (void)endDeferringViewInWindowChangesSync |
| { |
| _data->_impl->endDeferringViewInWindowChangesSync(); |
| } |
| |
| - (void)_prepareForMoveToWindow:(NSWindow *)targetWindow withCompletionHandler:(void(^)(void))completionHandler |
| { |
| _data->_impl->prepareForMoveToWindow(targetWindow, completionHandler); |
| } |
| |
| - (BOOL)isDeferringViewInWindowChanges |
| { |
| return _data->_impl->isDeferringViewInWindowChanges(); |
| } |
| |
| - (BOOL)windowOcclusionDetectionEnabled |
| { |
| return _data->_windowOcclusionDetectionEnabled; |
| } |
| |
| - (void)setWindowOcclusionDetectionEnabled:(BOOL)flag |
| { |
| _data->_windowOcclusionDetectionEnabled = flag; |
| } |
| |
| - (void)setAllowsBackForwardNavigationGestures:(BOOL)allowsBackForwardNavigationGestures |
| { |
| _data->_impl->setAllowsBackForwardNavigationGestures(allowsBackForwardNavigationGestures); |
| } |
| |
| - (BOOL)allowsBackForwardNavigationGestures |
| { |
| return _data->_impl->allowsBackForwardNavigationGestures(); |
| } |
| |
| - (BOOL)allowsLinkPreview |
| { |
| return _data->_impl->allowsLinkPreview(); |
| } |
| |
| - (void)setAllowsLinkPreview:(BOOL)allowsLinkPreview |
| { |
| _data->_impl->setAllowsLinkPreview(allowsLinkPreview); |
| } |
| |
| - (void)_setIgnoresAllEvents:(BOOL)ignoresAllEvents |
| { |
| _data->_impl->setIgnoresAllEvents(ignoresAllEvents); |
| } |
| |
| // Forward _setIgnoresNonWheelMouseEvents to _setIgnoresNonWheelEvents to avoid breaking existing clients. |
| - (void)_setIgnoresNonWheelMouseEvents:(BOOL)ignoresNonWheelMouseEvents |
| { |
| _data->_impl->setIgnoresNonWheelEvents(ignoresNonWheelMouseEvents); |
| } |
| |
| - (void)_setIgnoresNonWheelEvents:(BOOL)ignoresNonWheelEvents |
| { |
| _data->_impl->setIgnoresNonWheelEvents(ignoresNonWheelEvents); |
| } |
| |
| - (BOOL)_ignoresNonWheelEvents |
| { |
| return _data->_impl->ignoresNonWheelEvents(); |
| } |
| |
| - (BOOL)_ignoresAllEvents |
| { |
| return _data->_impl->ignoresAllEvents(); |
| } |
| |
| - (void)_setOverrideDeviceScaleFactor:(CGFloat)deviceScaleFactor |
| { |
| _data->_impl->setOverrideDeviceScaleFactor(deviceScaleFactor); |
| } |
| |
| - (CGFloat)_overrideDeviceScaleFactor |
| { |
| return _data->_impl->overrideDeviceScaleFactor(); |
| } |
| |
| - (WKLayoutMode)_layoutMode |
| { |
| return _data->_impl->layoutMode(); |
| } |
| |
| - (void)_setLayoutMode:(WKLayoutMode)layoutMode |
| { |
| _data->_impl->setLayoutMode(layoutMode); |
| } |
| |
| - (CGSize)_fixedLayoutSize |
| { |
| return _data->_impl->fixedLayoutSize(); |
| } |
| |
| - (void)_setFixedLayoutSize:(CGSize)fixedLayoutSize |
| { |
| _data->_impl->setFixedLayoutSize(fixedLayoutSize); |
| } |
| |
| - (CGFloat)_viewScale |
| { |
| return _data->_impl->viewScale(); |
| } |
| |
| - (void)_setViewScale:(CGFloat)viewScale |
| { |
| _data->_impl->setViewScale(viewScale); |
| } |
| |
| - (void)_setTopContentInset:(CGFloat)contentInset |
| { |
| return _data->_impl->setTopContentInset(contentInset); |
| } |
| |
| - (CGFloat)_topContentInset |
| { |
| return _data->_impl->topContentInset(); |
| } |
| |
| - (void)_setTotalHeightOfBanners:(CGFloat)totalHeightOfBanners |
| { |
| _data->_totalHeightOfBanners = totalHeightOfBanners; |
| } |
| |
| - (CGFloat)_totalHeightOfBanners |
| { |
| return _data->_totalHeightOfBanners; |
| } |
| |
| - (void)_setOverlayScrollbarStyle:(_WKOverlayScrollbarStyle)scrollbarStyle |
| { |
| WTF::Optional<WebCore::ScrollbarOverlayStyle> coreScrollbarStyle; |
| |
| switch (scrollbarStyle) { |
| case _WKOverlayScrollbarStyleDark: |
| coreScrollbarStyle = ScrollbarOverlayStyleDark; |
| break; |
| case _WKOverlayScrollbarStyleLight: |
| coreScrollbarStyle = ScrollbarOverlayStyleLight; |
| break; |
| case _WKOverlayScrollbarStyleDefault: |
| coreScrollbarStyle = ScrollbarOverlayStyleDefault; |
| break; |
| case _WKOverlayScrollbarStyleAutomatic: |
| default: |
| break; |
| } |
| |
| _data->_page->setOverlayScrollbarStyle(coreScrollbarStyle); |
| } |
| |
| - (_WKOverlayScrollbarStyle)_overlayScrollbarStyle |
| { |
| WTF::Optional<WebCore::ScrollbarOverlayStyle> coreScrollbarStyle = _data->_page->overlayScrollbarStyle(); |
| |
| if (!coreScrollbarStyle) |
| return _WKOverlayScrollbarStyleAutomatic; |
| |
| switch (coreScrollbarStyle.value()) { |
| case ScrollbarOverlayStyleDark: |
| return _WKOverlayScrollbarStyleDark; |
| case ScrollbarOverlayStyleLight: |
| return _WKOverlayScrollbarStyleLight; |
| case ScrollbarOverlayStyleDefault: |
| return _WKOverlayScrollbarStyleDefault; |
| default: |
| return _WKOverlayScrollbarStyleAutomatic; |
| } |
| } |
| |
| - (NSColor *)_pageExtendedBackgroundColor |
| { |
| WebCore::Color color = _data->_page->pageExtendedBackgroundColor(); |
| if (!color.isValid()) |
| return nil; |
| |
| return nsColor(color); |
| } |
| |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Wmissing-noreturn" |
| // This method forces a drawing area geometry update, even if frame size updates are disabled. |
| // The updated is performed asynchronously; we don't wait for the geometry update before returning. |
| // The area drawn need not match the current frame size - if it differs it will be anchored to the |
| // frame according to the current contentAnchor. |
| - (void)forceAsyncDrawingAreaSizeUpdate:(NSSize)size |
| { |
| // This SPI is only used on 10.9 and below, and is incompatible with the fence-based drawing area size synchronization in 10.10+. |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED <= 1090 |
| if (_data->_impl->clipsToVisibleRect()) |
| _data->_impl->updateViewExposedRect(); |
| _data->_impl->setDrawingAreaSize(NSSizeToCGSize(size)); |
| |
| // If a geometry update is pending the new update won't be sent. Poll without waiting for any |
| // pending did-update message now, such that the new update can be sent. We do so after setting |
| // the drawing area size such that the latest update is sent. |
| if (DrawingAreaProxy* drawingArea = _data->_page->drawingArea()) |
| drawingArea->waitForPossibleGeometryUpdate(std::chrono::milliseconds::zero()); |
| #else |
| ASSERT_NOT_REACHED(); |
| #endif |
| } |
| |
| - (void)waitForAsyncDrawingAreaSizeUpdate |
| { |
| // This SPI is only used on 10.9 and below, and is incompatible with the fence-based drawing area size synchronization in 10.10+. |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED <= 1090 |
| if (DrawingAreaProxy* drawingArea = _data->_page->drawingArea()) { |
| // If a geometry update is still pending then the action of receiving the |
| // first geometry update may result in another update being scheduled - |
| // we should wait for this to complete too. |
| drawingArea->waitForPossibleGeometryUpdate(DrawingAreaProxy::didUpdateBackingStoreStateTimeout() / 2); |
| drawingArea->waitForPossibleGeometryUpdate(DrawingAreaProxy::didUpdateBackingStoreStateTimeout() / 2); |
| } |
| #else |
| ASSERT_NOT_REACHED(); |
| #endif |
| } |
| #pragma clang diagnostic pop |
| |
| - (BOOL)isUsingUISideCompositing |
| { |
| if (DrawingAreaProxy* drawingArea = _data->_page->drawingArea()) |
| return drawingArea->type() == DrawingAreaTypeRemoteLayerTree; |
| |
| return NO; |
| } |
| |
| - (void)setAllowsMagnification:(BOOL)allowsMagnification |
| { |
| _data->_impl->setAllowsMagnification(allowsMagnification); |
| } |
| |
| - (BOOL)allowsMagnification |
| { |
| return _data->_impl->allowsMagnification(); |
| } |
| |
| - (void)magnifyWithEvent:(NSEvent *)event |
| { |
| _data->_impl->magnifyWithEvent(event); |
| } |
| |
| #if ENABLE(MAC_GESTURE_EVENTS) |
| - (void)rotateWithEvent:(NSEvent *)event |
| { |
| _data->_impl->rotateWithEvent(event); |
| } |
| #endif |
| |
| - (void)_gestureEventWasNotHandledByWebCore:(NSEvent *)event |
| { |
| _data->_impl->gestureEventWasNotHandledByWebCoreFromViewOnly(event); |
| } |
| |
| - (void)smartMagnifyWithEvent:(NSEvent *)event |
| { |
| _data->_impl->smartMagnifyWithEvent(event); |
| } |
| |
| - (void)setMagnification:(double)magnification centeredAtPoint:(NSPoint)point |
| { |
| _data->_impl->setMagnification(magnification, NSPointToCGPoint(point)); |
| } |
| |
| - (void)setMagnification:(double)magnification |
| { |
| _data->_impl->setMagnification(magnification); |
| } |
| |
| - (double)magnification |
| { |
| return _data->_impl->magnification(); |
| } |
| |
| - (void)_setCustomSwipeViews:(NSArray *)customSwipeViews |
| { |
| _data->_impl->setCustomSwipeViews(customSwipeViews); |
| } |
| |
| - (void)_setCustomSwipeViewsTopContentInset:(float)topContentInset |
| { |
| _data->_impl->setCustomSwipeViewsTopContentInset(topContentInset); |
| } |
| |
| - (BOOL)_tryToSwipeWithEvent:(NSEvent *)event ignoringPinnedState:(BOOL)ignoringPinnedState |
| { |
| return _data->_impl->tryToSwipeWithEvent(event, ignoringPinnedState); |
| } |
| |
| - (void)_setDidMoveSwipeSnapshotCallback:(void(^)(CGRect))callback |
| { |
| _data->_impl->setDidMoveSwipeSnapshotCallback(callback); |
| } |
| |
| - (id)_immediateActionAnimationControllerForHitTestResult:(WKHitTestResultRef)hitTestResult withType:(uint32_t)type userData:(WKTypeRef)userData |
| { |
| return nil; |
| } |
| |
| - (void)_prepareForImmediateActionAnimation |
| { |
| } |
| |
| - (void)_cancelImmediateActionAnimation |
| { |
| } |
| |
| - (void)_completeImmediateActionAnimation |
| { |
| } |
| |
| - (void)_didChangeContentSize:(NSSize)newSize |
| { |
| } |
| |
| - (void)_dismissContentRelativeChildWindows |
| { |
| _data->_impl->dismissContentRelativeChildWindowsFromViewOnly(); |
| } |
| |
| - (void)_dismissContentRelativeChildWindowsWithAnimation:(BOOL)withAnimation |
| { |
| _data->_impl->dismissContentRelativeChildWindowsWithAnimationFromViewOnly(withAnimation); |
| } |
| |
| #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000 |
| |
| - (void)_setAutomaticallyAdjustsContentInsets:(BOOL)automaticallyAdjustsContentInsets |
| { |
| _data->_impl->setAutomaticallyAdjustsContentInsets(automaticallyAdjustsContentInsets); |
| } |
| |
| - (BOOL)_automaticallyAdjustsContentInsets |
| { |
| return _data->_impl->automaticallyAdjustsContentInsets(); |
| } |
| |
| #endif |
| |
| @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 |
| |
| #endif // PLATFORM(MAC) |