| /* |
| * Copyright (C) 2005-2020 Apple Inc. All rights reserved. |
| * (C) 2006, 2007 Graham Dennis (graham.dennis@gmail.com) |
| * |
| * 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. |
| * 3. Neither the name of Apple Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE 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 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 "WebHTMLView.h" |
| |
| #import "DOMCSSStyleDeclarationInternal.h" |
| #import "DOMDocumentFragmentInternal.h" |
| #import "DOMDocumentInternal.h" |
| #import "DOMNodeInternal.h" |
| #import "DOMRangeInternal.h" |
| #import "WebArchive.h" |
| #import "WebClipView.h" |
| #import "WebContextMenuClient.h" |
| #import "WebDOMOperationsInternal.h" |
| #import "WebDataSourceInternal.h" |
| #import "WebDefaultUIDelegate.h" |
| #import "WebDelegateImplementationCaching.h" |
| #import "WebDocumentInternal.h" |
| #import "WebDynamicScrollBarsViewInternal.h" |
| #import "WebEditingDelegate.h" |
| #import "WebElementDictionary.h" |
| #import "WebFrameInternal.h" |
| #import "WebFramePrivate.h" |
| #import "WebFrameViewInternal.h" |
| #import "WebHTMLRepresentationPrivate.h" |
| #import "WebHTMLViewInternal.h" |
| #import "WebImmediateActionController.h" |
| #import "WebKitLogging.h" |
| #import "WebKitNSStringExtras.h" |
| #import "WebKitVersionChecks.h" |
| #import "WebLocalizableStringsInternal.h" |
| #import "WebNSFileManagerExtras.h" |
| #import "WebNSImageExtras.h" |
| #import "WebNSObjectExtras.h" |
| #import "WebNSPrintOperationExtras.h" |
| #import "WebNSURLExtras.h" |
| #import "WebNSViewExtras.h" |
| #import "WebNodeHighlight.h" |
| #import "WebPluginController.h" |
| #import "WebPreferences.h" |
| #import "WebPreferencesPrivate.h" |
| #import "WebResourcePrivate.h" |
| #import "WebSharingServicePickerController.h" |
| #import "WebTextCompletionController.h" |
| #import "WebUIDelegatePrivate.h" |
| #import "WebViewInternal.h" |
| #import <JavaScriptCore/InitializeThreading.h> |
| #import <QuartzCore/QuartzCore.h> |
| #import <WebCore/CSSStyleDeclaration.h> |
| #import <WebCore/CachedImage.h> |
| #import <WebCore/CachedResourceClient.h> |
| #import <WebCore/CachedResourceLoader.h> |
| #import <WebCore/Chrome.h> |
| #import <WebCore/ColorMac.h> |
| #import <WebCore/CompositionHighlight.h> |
| #import <WebCore/ContextMenu.h> |
| #import <WebCore/ContextMenuController.h> |
| #import <WebCore/DictationAlternative.h> |
| #import <WebCore/DictionaryLookup.h> |
| #import <WebCore/Document.h> |
| #import <WebCore/DocumentFragment.h> |
| #import <WebCore/DocumentMarkerController.h> |
| #import <WebCore/DragController.h> |
| #import <WebCore/DragImage.h> |
| #import <WebCore/Editor.h> |
| #import <WebCore/EditorDeleteAction.h> |
| #import <WebCore/Element.h> |
| #import <WebCore/EventHandler.h> |
| #import <WebCore/FloatRect.h> |
| #import <WebCore/FocusController.h> |
| #import <WebCore/Font.h> |
| #import <WebCore/FontAttributeChanges.h> |
| #import <WebCore/FontAttributes.h> |
| #import <WebCore/FontCache.h> |
| #import <WebCore/Frame.h> |
| #import <WebCore/FrameLoader.h> |
| #import <WebCore/FrameSelection.h> |
| #import <WebCore/FrameView.h> |
| #import <WebCore/HTMLConverter.h> |
| #import <WebCore/HTMLNames.h> |
| #import <WebCore/HitTestResult.h> |
| #import <WebCore/Image.h> |
| #import <WebCore/KeyboardEvent.h> |
| #import <WebCore/LegacyNSPasteboardTypes.h> |
| #import <WebCore/LegacyWebArchive.h> |
| #import <WebCore/LocalizedStrings.h> |
| #import <WebCore/MIMETypeRegistry.h> |
| #import <WebCore/Page.h> |
| #import <WebCore/PrintContext.h> |
| #import <WebCore/Range.h> |
| #import <WebCore/RenderView.h> |
| #import <WebCore/RenderWidget.h> |
| #import <WebCore/RuntimeApplicationChecks.h> |
| #import <WebCore/RuntimeEnabledFeatures.h> |
| #import <WebCore/SharedBuffer.h> |
| #import <WebCore/StyleProperties.h> |
| #import <WebCore/StyleScope.h> |
| #import <WebCore/Text.h> |
| #import <WebCore/TextAlternativeWithRange.h> |
| #import <WebCore/TextIndicator.h> |
| #import <WebCore/TextUndoInsertionMarkupMac.h> |
| #import <WebCore/WebCoreJITOperations.h> |
| #import <WebCore/WebCoreNSFontManagerExtras.h> |
| #import <WebCore/WebCoreObjCExtras.h> |
| #import <WebCore/WebNSAttributedStringExtras.h> |
| #import <WebCore/markup.h> |
| #import <WebKitLegacy/DOM.h> |
| #import <WebKitLegacy/DOMExtensions.h> |
| #import <WebKitLegacy/DOMPrivate.h> |
| #import <dlfcn.h> |
| #import <limits> |
| #import <pal/spi/cf/CFUtilitiesSPI.h> |
| #import <pal/spi/cocoa/NSAttributedStringSPI.h> |
| #import <pal/spi/cocoa/NSURLFileTypeMappingsSPI.h> |
| #import <pal/spi/mac/NSMenuSPI.h> |
| #import <pal/spi/mac/NSScrollerImpSPI.h> |
| #import <pal/spi/mac/NSSpellCheckerSPI.h> |
| #import <pal/spi/mac/NSViewSPI.h> |
| #import <pal/spi/mac/NSWindowSPI.h> |
| #import <wtf/BlockObjCExceptions.h> |
| #import <wtf/MainThread.h> |
| #import <wtf/MathExtras.h> |
| #import <wtf/NakedPtr.h> |
| #import <wtf/ObjCRuntimeExtras.h> |
| #import <wtf/RunLoop.h> |
| #import <wtf/SystemTracing.h> |
| #import <wtf/WeakObjCPtr.h> |
| #import <wtf/cocoa/TypeCastsCocoa.h> |
| #import <wtf/cocoa/VectorCocoa.h> |
| |
| #if PLATFORM(MAC) |
| #import "WebNSEventExtras.h" |
| #import "WebNSPasteboardExtras.h" |
| #import <AppKit/NSAccessibility.h> |
| #import <WebCore/PlatformEventFactoryMac.h> |
| #import <pal/spi/mac/NSMenuSPI.h> |
| #import <pal/spi/mac/NSTextInputContextSPI.h> |
| #endif |
| |
| #if PLATFORM(IOS_FAMILY) |
| #import "WebUIKitDelegate.h" |
| #import <WebCore/GraphicsContextCG.h> |
| #import <WebCore/KeyEventCodesIOS.h> |
| #import <WebCore/PlatformEventFactoryIOS.h> |
| #import <WebCore/WAKClipView.h> |
| #import <WebCore/WAKScrollView.h> |
| #import <WebCore/WAKWindow.h> |
| #import <WebCore/WKGraphics.h> |
| #import <WebCore/WebCoreThreadRun.h> |
| #import <WebCore/WebEvent.h> |
| #import <pal/spi/cf/CFNotificationCenterSPI.h> |
| #import <pal/spi/ios/GraphicsServicesSPI.h> |
| #endif |
| |
| #if PLATFORM(IOS_FAMILY) |
| |
| @interface NSObject (Accessibility) |
| - (id)accessibilityHitTest:(NSPoint)point; |
| - (id)accessibilityFocusedUIElement; |
| @end |
| |
| #endif |
| |
| #if PLATFORM(MAC) |
| |
| @class NSTextInputContext; |
| |
| @interface NSApplication () |
| - (BOOL)isSpeaking; |
| - (void)speakString:(NSString *)string; |
| - (void)stopSpeaking:(id)sender; |
| @end |
| |
| @interface NSAttributedString () |
| - (DOMDocumentFragment *)_documentFromRange:(NSRange)range document:(DOMDocument *)document documentAttributes:(NSDictionary *)dict subresources:(NSArray **)subresources; |
| @end |
| |
| @interface NSObject () |
| - (BOOL)handleMouseEvent:(NSEvent *)event; |
| - (BOOL)wantsToHandleMouseEvents; |
| @end |
| |
| @interface NSResponder () |
| - (NSTextInputContext *)inputContext; |
| @end |
| |
| @interface NSView () |
| - (BOOL)_drawnByAncestor; |
| - (void)_invalidateGStatesForTree; |
| - (void)_windowChangedKeyState; |
| @end |
| |
| @interface NSWindow () |
| @property (readonly) __kindof NSView *_borderView; |
| |
| - (id)_newFirstResponderAfterResigning; |
| @end |
| |
| using WebEvent = NSEvent; |
| const auto WebEventMouseDown = NSEventTypeLeftMouseDown; |
| |
| @interface WebMenuTarget : NSObject { |
| NakedPtr<WebCore::ContextMenuController> _menuController; |
| } |
| + (WebMenuTarget*)sharedMenuTarget; |
| - (NakedPtr<WebCore::ContextMenuController>)menuController; |
| - (void)setMenuController:(NakedPtr<WebCore::ContextMenuController>)menuController; |
| - (void)forwardContextMenuAction:(id)sender; |
| @end |
| |
| static std::optional<WebCore::ContextMenuAction> toAction(NSInteger tag) |
| { |
| using namespace WebCore; |
| if (tag >= ContextMenuItemBaseCustomTag && tag <= ContextMenuItemLastCustomTag) { |
| // Just pass these through. |
| return static_cast<ContextMenuAction>(tag); |
| } |
| |
| switch (tag) { |
| case WebMenuItemTagOpenLinkInNewWindow: |
| return ContextMenuItemTagOpenLinkInNewWindow; |
| case WebMenuItemTagDownloadLinkToDisk: |
| return ContextMenuItemTagDownloadLinkToDisk; |
| case WebMenuItemTagCopyLinkToClipboard: |
| return ContextMenuItemTagCopyLinkToClipboard; |
| case WebMenuItemTagOpenImageInNewWindow: |
| return ContextMenuItemTagOpenImageInNewWindow; |
| case WebMenuItemTagDownloadImageToDisk: |
| return ContextMenuItemTagDownloadImageToDisk; |
| case WebMenuItemTagCopyImageToClipboard: |
| return ContextMenuItemTagCopyImageToClipboard; |
| case WebMenuItemTagOpenFrameInNewWindow: |
| return ContextMenuItemTagOpenFrameInNewWindow; |
| case WebMenuItemTagCopy: |
| return ContextMenuItemTagCopy; |
| case WebMenuItemTagGoBack: |
| return ContextMenuItemTagGoBack; |
| case WebMenuItemTagGoForward: |
| return ContextMenuItemTagGoForward; |
| case WebMenuItemTagStop: |
| return ContextMenuItemTagStop; |
| case WebMenuItemTagReload: |
| return ContextMenuItemTagReload; |
| case WebMenuItemTagCut: |
| return ContextMenuItemTagCut; |
| case WebMenuItemTagPaste: |
| return ContextMenuItemTagPaste; |
| case WebMenuItemTagSpellingGuess: |
| return ContextMenuItemTagSpellingGuess; |
| case WebMenuItemTagNoGuessesFound: |
| return ContextMenuItemTagNoGuessesFound; |
| case WebMenuItemTagIgnoreSpelling: |
| return ContextMenuItemTagIgnoreSpelling; |
| case WebMenuItemTagLearnSpelling: |
| return ContextMenuItemTagLearnSpelling; |
| case WebMenuItemTagOther: |
| return ContextMenuItemTagOther; |
| case WebMenuItemTagSearchInSpotlight: |
| return ContextMenuItemTagSearchInSpotlight; |
| case WebMenuItemTagSearchWeb: |
| return ContextMenuItemTagSearchWeb; |
| case WebMenuItemTagLookUpInDictionary: |
| return ContextMenuItemTagLookUpInDictionary; |
| case WebMenuItemTagOpenWithDefaultApplication: |
| return ContextMenuItemTagOpenWithDefaultApplication; |
| case WebMenuItemPDFActualSize: |
| return ContextMenuItemPDFActualSize; |
| case WebMenuItemPDFZoomIn: |
| return ContextMenuItemPDFZoomIn; |
| case WebMenuItemPDFZoomOut: |
| return ContextMenuItemPDFZoomOut; |
| case WebMenuItemPDFAutoSize: |
| return ContextMenuItemPDFAutoSize; |
| case WebMenuItemPDFSinglePage: |
| return ContextMenuItemPDFSinglePage; |
| case WebMenuItemPDFFacingPages: |
| return ContextMenuItemPDFFacingPages; |
| case WebMenuItemPDFContinuous: |
| return ContextMenuItemPDFContinuous; |
| case WebMenuItemPDFNextPage: |
| return ContextMenuItemPDFNextPage; |
| case WebMenuItemPDFPreviousPage: |
| return ContextMenuItemPDFPreviousPage; |
| case WebMenuItemTagOpenLink: |
| return ContextMenuItemTagOpenLink; |
| case WebMenuItemTagIgnoreGrammar: |
| return ContextMenuItemTagIgnoreGrammar; |
| case WebMenuItemTagSpellingMenu: |
| return ContextMenuItemTagSpellingMenu; |
| case WebMenuItemTagShowSpellingPanel: |
| return ContextMenuItemTagShowSpellingPanel; |
| case WebMenuItemTagCheckSpelling: |
| return ContextMenuItemTagCheckSpelling; |
| case WebMenuItemTagCheckSpellingWhileTyping: |
| return ContextMenuItemTagCheckSpellingWhileTyping; |
| case WebMenuItemTagCheckGrammarWithSpelling: |
| return ContextMenuItemTagCheckGrammarWithSpelling; |
| case WebMenuItemTagFontMenu: |
| return ContextMenuItemTagFontMenu; |
| case WebMenuItemTagShowFonts: |
| return ContextMenuItemTagShowFonts; |
| case WebMenuItemTagBold: |
| return ContextMenuItemTagBold; |
| case WebMenuItemTagItalic: |
| return ContextMenuItemTagItalic; |
| case WebMenuItemTagUnderline: |
| return ContextMenuItemTagUnderline; |
| case WebMenuItemTagOutline: |
| return ContextMenuItemTagOutline; |
| case WebMenuItemTagStyles: |
| return ContextMenuItemTagStyles; |
| case WebMenuItemTagShowColors: |
| return ContextMenuItemTagShowColors; |
| case WebMenuItemTagSpeechMenu: |
| return ContextMenuItemTagSpeechMenu; |
| case WebMenuItemTagStartSpeaking: |
| return ContextMenuItemTagStartSpeaking; |
| case WebMenuItemTagStopSpeaking: |
| return ContextMenuItemTagStopSpeaking; |
| case WebMenuItemTagWritingDirectionMenu: |
| return ContextMenuItemTagWritingDirectionMenu; |
| case WebMenuItemTagDefaultDirection: |
| return ContextMenuItemTagDefaultDirection; |
| case WebMenuItemTagLeftToRight: |
| return ContextMenuItemTagLeftToRight; |
| case WebMenuItemTagRightToLeft: |
| return ContextMenuItemTagRightToLeft; |
| case WebMenuItemPDFSinglePageScrolling: |
| return ContextMenuItemTagPDFSinglePageScrolling; |
| case WebMenuItemPDFFacingPagesScrolling: |
| return ContextMenuItemTagPDFFacingPagesScrolling; |
| case WebMenuItemTagInspectElement: |
| return ContextMenuItemTagInspectElement; |
| case WebMenuItemTagTextDirectionMenu: |
| return ContextMenuItemTagTextDirectionMenu; |
| case WebMenuItemTagTextDirectionDefault: |
| return ContextMenuItemTagTextDirectionDefault; |
| case WebMenuItemTagTextDirectionLeftToRight: |
| return ContextMenuItemTagTextDirectionLeftToRight; |
| case WebMenuItemTagTextDirectionRightToLeft: |
| return ContextMenuItemTagTextDirectionRightToLeft; |
| case WebMenuItemTagCorrectSpellingAutomatically: |
| return ContextMenuItemTagCorrectSpellingAutomatically; |
| case WebMenuItemTagSubstitutionsMenu: |
| return ContextMenuItemTagSubstitutionsMenu; |
| case WebMenuItemTagShowSubstitutions: |
| return ContextMenuItemTagShowSubstitutions; |
| case WebMenuItemTagSmartCopyPaste: |
| return ContextMenuItemTagSmartCopyPaste; |
| case WebMenuItemTagSmartQuotes: |
| return ContextMenuItemTagSmartQuotes; |
| case WebMenuItemTagSmartDashes: |
| return ContextMenuItemTagSmartDashes; |
| case WebMenuItemTagSmartLinks: |
| return ContextMenuItemTagSmartLinks; |
| case WebMenuItemTagTextReplacement: |
| return ContextMenuItemTagTextReplacement; |
| case WebMenuItemTagTransformationsMenu: |
| return ContextMenuItemTagTransformationsMenu; |
| case WebMenuItemTagMakeUpperCase: |
| return ContextMenuItemTagMakeUpperCase; |
| case WebMenuItemTagMakeLowerCase: |
| return ContextMenuItemTagMakeLowerCase; |
| case WebMenuItemTagCapitalize: |
| return ContextMenuItemTagCapitalize; |
| case WebMenuItemTagChangeBack: |
| return ContextMenuItemTagChangeBack; |
| case WebMenuItemTagOpenMediaInNewWindow: |
| return ContextMenuItemTagOpenMediaInNewWindow; |
| case WebMenuItemTagCopyMediaLinkToClipboard: |
| return ContextMenuItemTagCopyMediaLinkToClipboard; |
| case WebMenuItemTagToggleMediaControls: |
| return ContextMenuItemTagToggleMediaControls; |
| case WebMenuItemTagToggleMediaLoop: |
| return ContextMenuItemTagToggleMediaLoop; |
| case WebMenuItemTagEnterVideoFullscreen: |
| return ContextMenuItemTagEnterVideoFullscreen; |
| case WebMenuItemTagToggleVideoEnhancedFullscreen: |
| return ContextMenuItemTagToggleVideoEnhancedFullscreen; |
| case WebMenuItemTagMediaPlayPause: |
| return ContextMenuItemTagMediaPlayPause; |
| case WebMenuItemTagMediaMute: |
| return ContextMenuItemTagMediaMute; |
| case WebMenuItemTagDictationAlternative: |
| return ContextMenuItemTagDictationAlternative; |
| case WebMenuItemTagTranslate: |
| return ContextMenuItemTagTranslate; |
| } |
| return std::nullopt; |
| } |
| |
| static std::optional<NSInteger> toTag(WebCore::ContextMenuAction action) |
| { |
| using namespace WebCore; |
| switch (action) { |
| case ContextMenuItemTagNoAction: |
| return std::nullopt; |
| |
| case ContextMenuItemTagOpenLinkInNewWindow: |
| return WebMenuItemTagOpenLinkInNewWindow; |
| case ContextMenuItemTagDownloadLinkToDisk: |
| return WebMenuItemTagDownloadLinkToDisk; |
| case ContextMenuItemTagCopyLinkToClipboard: |
| return WebMenuItemTagCopyLinkToClipboard; |
| case ContextMenuItemTagOpenImageInNewWindow: |
| return WebMenuItemTagOpenImageInNewWindow; |
| case ContextMenuItemTagDownloadImageToDisk: |
| return WebMenuItemTagDownloadImageToDisk; |
| case ContextMenuItemTagCopyImageToClipboard: |
| return WebMenuItemTagCopyImageToClipboard; |
| case ContextMenuItemTagOpenFrameInNewWindow: |
| return WebMenuItemTagOpenFrameInNewWindow; |
| case ContextMenuItemTagCopy: |
| return WebMenuItemTagCopy; |
| case ContextMenuItemTagGoBack: |
| return WebMenuItemTagGoBack; |
| case ContextMenuItemTagGoForward: |
| return WebMenuItemTagGoForward; |
| case ContextMenuItemTagStop: |
| return WebMenuItemTagStop; |
| case ContextMenuItemTagReload: |
| return WebMenuItemTagReload; |
| case ContextMenuItemTagCut: |
| return WebMenuItemTagCut; |
| case ContextMenuItemTagPaste: |
| return WebMenuItemTagPaste; |
| case ContextMenuItemTagSpellingGuess: |
| return WebMenuItemTagSpellingGuess; |
| case ContextMenuItemTagNoGuessesFound: |
| return WebMenuItemTagNoGuessesFound; |
| case ContextMenuItemTagIgnoreSpelling: |
| return WebMenuItemTagIgnoreSpelling; |
| case ContextMenuItemTagLearnSpelling: |
| return WebMenuItemTagLearnSpelling; |
| case ContextMenuItemTagOther: |
| return WebMenuItemTagOther; |
| case ContextMenuItemTagSearchInSpotlight: |
| return WebMenuItemTagSearchInSpotlight; |
| case ContextMenuItemTagSearchWeb: |
| return WebMenuItemTagSearchWeb; |
| case ContextMenuItemTagLookUpInDictionary: |
| return WebMenuItemTagLookUpInDictionary; |
| case ContextMenuItemTagOpenWithDefaultApplication: |
| return WebMenuItemTagOpenWithDefaultApplication; |
| case ContextMenuItemPDFActualSize: |
| return WebMenuItemPDFActualSize; |
| case ContextMenuItemPDFZoomIn: |
| return WebMenuItemPDFZoomIn; |
| case ContextMenuItemPDFZoomOut: |
| return WebMenuItemPDFZoomOut; |
| case ContextMenuItemPDFAutoSize: |
| return WebMenuItemPDFAutoSize; |
| case ContextMenuItemPDFSinglePage: |
| return WebMenuItemPDFSinglePage; |
| case ContextMenuItemPDFFacingPages: |
| return WebMenuItemPDFFacingPages; |
| case ContextMenuItemPDFContinuous: |
| return WebMenuItemPDFContinuous; |
| case ContextMenuItemPDFNextPage: |
| return WebMenuItemPDFNextPage; |
| case ContextMenuItemPDFPreviousPage: |
| return WebMenuItemPDFPreviousPage; |
| case ContextMenuItemTagOpenLink: |
| return WebMenuItemTagOpenLink; |
| case ContextMenuItemTagIgnoreGrammar: |
| return WebMenuItemTagIgnoreGrammar; |
| case ContextMenuItemTagSpellingMenu: |
| return WebMenuItemTagSpellingMenu; |
| case ContextMenuItemTagShowSpellingPanel: |
| return WebMenuItemTagShowSpellingPanel; |
| case ContextMenuItemTagCheckSpelling: |
| return WebMenuItemTagCheckSpelling; |
| case ContextMenuItemTagCheckSpellingWhileTyping: |
| return WebMenuItemTagCheckSpellingWhileTyping; |
| case ContextMenuItemTagCheckGrammarWithSpelling: |
| return WebMenuItemTagCheckGrammarWithSpelling; |
| case ContextMenuItemTagFontMenu: |
| return WebMenuItemTagFontMenu; |
| case ContextMenuItemTagShowFonts: |
| return WebMenuItemTagShowFonts; |
| case ContextMenuItemTagBold: |
| return WebMenuItemTagBold; |
| case ContextMenuItemTagItalic: |
| return WebMenuItemTagItalic; |
| case ContextMenuItemTagUnderline: |
| return WebMenuItemTagUnderline; |
| case ContextMenuItemTagOutline: |
| return WebMenuItemTagOutline; |
| case ContextMenuItemTagStyles: |
| return WebMenuItemTagStyles; |
| case ContextMenuItemTagShowColors: |
| return WebMenuItemTagShowColors; |
| case ContextMenuItemTagSpeechMenu: |
| return WebMenuItemTagSpeechMenu; |
| case ContextMenuItemTagStartSpeaking: |
| return WebMenuItemTagStartSpeaking; |
| case ContextMenuItemTagStopSpeaking: |
| return WebMenuItemTagStopSpeaking; |
| case ContextMenuItemTagWritingDirectionMenu: |
| return WebMenuItemTagWritingDirectionMenu; |
| case ContextMenuItemTagDefaultDirection: |
| return WebMenuItemTagDefaultDirection; |
| case ContextMenuItemTagLeftToRight: |
| return WebMenuItemTagLeftToRight; |
| case ContextMenuItemTagRightToLeft: |
| return WebMenuItemTagRightToLeft; |
| case ContextMenuItemTagPDFSinglePageScrolling: |
| return WebMenuItemPDFSinglePageScrolling; |
| case ContextMenuItemTagPDFFacingPagesScrolling: |
| return WebMenuItemPDFFacingPagesScrolling; |
| case ContextMenuItemTagInspectElement: |
| return WebMenuItemTagInspectElement; |
| case ContextMenuItemTagTextDirectionMenu: |
| return WebMenuItemTagTextDirectionMenu; |
| case ContextMenuItemTagTextDirectionDefault: |
| return WebMenuItemTagTextDirectionDefault; |
| case ContextMenuItemTagTextDirectionLeftToRight: |
| return WebMenuItemTagTextDirectionLeftToRight; |
| case ContextMenuItemTagTextDirectionRightToLeft: |
| return WebMenuItemTagTextDirectionRightToLeft; |
| case ContextMenuItemTagCorrectSpellingAutomatically: |
| return WebMenuItemTagCorrectSpellingAutomatically; |
| case ContextMenuItemTagSubstitutionsMenu: |
| return WebMenuItemTagSubstitutionsMenu; |
| case ContextMenuItemTagShowSubstitutions: |
| return WebMenuItemTagShowSubstitutions; |
| case ContextMenuItemTagSmartCopyPaste: |
| return WebMenuItemTagSmartCopyPaste; |
| case ContextMenuItemTagSmartQuotes: |
| return WebMenuItemTagSmartQuotes; |
| case ContextMenuItemTagSmartDashes: |
| return WebMenuItemTagSmartDashes; |
| case ContextMenuItemTagSmartLinks: |
| return WebMenuItemTagSmartLinks; |
| case ContextMenuItemTagTextReplacement: |
| return WebMenuItemTagTextReplacement; |
| case ContextMenuItemTagTransformationsMenu: |
| return WebMenuItemTagTransformationsMenu; |
| case ContextMenuItemTagMakeUpperCase: |
| return WebMenuItemTagMakeUpperCase; |
| case ContextMenuItemTagMakeLowerCase: |
| return WebMenuItemTagMakeLowerCase; |
| case ContextMenuItemTagCapitalize: |
| return WebMenuItemTagCapitalize; |
| case ContextMenuItemTagChangeBack: |
| return WebMenuItemTagChangeBack; |
| case ContextMenuItemTagOpenMediaInNewWindow: |
| return WebMenuItemTagOpenMediaInNewWindow; |
| case ContextMenuItemTagDownloadMediaToDisk: |
| return WebMenuItemTagDownloadMediaToDisk; |
| case ContextMenuItemTagCopyMediaLinkToClipboard: |
| return WebMenuItemTagCopyMediaLinkToClipboard; |
| case ContextMenuItemTagToggleMediaControls: |
| return WebMenuItemTagToggleMediaControls; |
| case ContextMenuItemTagToggleMediaLoop: |
| return WebMenuItemTagToggleMediaLoop; |
| case ContextMenuItemTagEnterVideoFullscreen: |
| return WebMenuItemTagEnterVideoFullscreen; |
| case ContextMenuItemTagMediaPlayPause: |
| return WebMenuItemTagMediaPlayPause; |
| case ContextMenuItemTagMediaMute: |
| return WebMenuItemTagMediaMute; |
| case ContextMenuItemTagDictationAlternative: |
| return WebMenuItemTagDictationAlternative; |
| case ContextMenuItemTagToggleVideoFullscreen: |
| return WebMenuItemTagToggleVideoFullscreen; |
| case ContextMenuItemTagAddHighlightToCurrentQuickNote: |
| case ContextMenuItemTagAddHighlightToNewQuickNote: |
| return std::nullopt; |
| case ContextMenuItemTagShareMenu: |
| return WebMenuItemTagShareMenu; |
| case ContextMenuItemTagToggleVideoEnhancedFullscreen: |
| return WebMenuItemTagToggleVideoEnhancedFullscreen; |
| case ContextMenuItemTagTranslate: |
| return WebMenuItemTagTranslate; |
| case ContextMenuItemTagCopySubject: |
| case ContextMenuItemTagLookUpImage: |
| return std::nullopt; |
| |
| case ContextMenuItemBaseCustomTag ... ContextMenuItemLastCustomTag: |
| // We just pass these through. |
| return static_cast<NSInteger>(action); |
| |
| case ContextMenuItemBaseApplicationTag: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| return std::nullopt; |
| } |
| |
| @implementation WebMenuTarget |
| |
| + (WebMenuTarget *)sharedMenuTarget |
| { |
| static WebMenuTarget *target = [[WebMenuTarget alloc] init]; |
| return target; |
| } |
| |
| - (NakedPtr<WebCore::ContextMenuController>)menuController |
| { |
| return _menuController; |
| } |
| |
| - (void)setMenuController:(NakedPtr<WebCore::ContextMenuController>)menuController |
| { |
| _menuController = menuController; |
| } |
| |
| - (void)forwardContextMenuAction:(id)sender |
| { |
| if (auto action = toAction([sender tag])) |
| _menuController->contextMenuItemSelected(*action, [sender title]); |
| } |
| |
| @end |
| |
| @interface WebResponderChainSink : NSResponder { |
| NSResponder* _lastResponderInChain; |
| BOOL _receivedUnhandledCommand; |
| } |
| - (id)initWithResponderChain:(NSResponder *)chain; |
| - (void)detach; |
| - (BOOL)receivedUnhandledCommand; |
| @end |
| |
| @interface WebLayerHostingFlippedView : NSView |
| @end |
| |
| @implementation WebLayerHostingFlippedView |
| |
| - (BOOL)isFlipped |
| { |
| return YES; |
| } |
| |
| @end |
| |
| @interface WebRootLayer : CALayer |
| @end |
| |
| @implementation WebRootLayer |
| |
| - (void)renderInContext:(CGContextRef)graphicsContext |
| { |
| // AppKit calls -[CALayer renderInContext:] to render layer-backed views |
| // into bitmap contexts, but renderInContext: doesn't capture mask layers |
| // (<rdar://problem/9539526>), so we can't rely on it. Since our layer |
| // contents will have already been rendered by drawRect:, we can safely make |
| // this a NOOP. |
| } |
| |
| @end |
| |
| // if YES, do the standard NSView hit test (which can't give the right result when HTML overlaps a view) |
| static BOOL forceNSViewHitTest; |
| |
| // if YES, do the "top WebHTMLView" hit test (which we'd like to do all the time but can't because of Java requirements [see bug 4349721]) |
| static BOOL forceWebHTMLViewHitTest; |
| |
| static WebHTMLView *lastHitView; |
| |
| static bool needsCursorRectsSupportAtPoint(NSWindow* window, NSPoint point) |
| { |
| forceNSViewHitTest = YES; |
| NSView* view = [window._borderView hitTest:point]; |
| forceNSViewHitTest = NO; |
| |
| // WebHTMLView doesn't use cursor rects. |
| if ([view isKindOfClass:[WebHTMLView class]]) |
| return false; |
| |
| // Non-Web content, WebPDFView, and WebKit plug-ins use normal cursor handling. |
| return true; |
| } |
| |
| static IMP oldSetCursorForMouseLocationIMP; |
| |
| // Overriding an internal method is a hack; <rdar://problem/7662987> tracks finding a better solution. |
| static void setCursor(NSWindow *self, SEL cmd, NSPoint point) |
| { |
| if (needsCursorRectsSupportAtPoint(self, point)) |
| wtfCallIMP<id>(oldSetCursorForMouseLocationIMP, self, cmd, point); |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| @interface NSView () |
| - (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView; |
| - (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect; |
| #if PLATFORM(MAC) |
| - (void)_recursive:(BOOL)recursive displayRectIgnoringOpacity:(NSRect)displayRect inContext:(NSGraphicsContext *)graphicsContext stopAtLayerBackedViews:(BOOL)stopAtLayerBackedViews; |
| #endif |
| - (void)_setDrawsOwnDescendants:(BOOL)drawsOwnDescendants; |
| #if PLATFORM(IOS_FAMILY) |
| - (void)centerSelectionInVisibleArea:(id)sender; |
| #endif |
| @end |
| |
| #if PLATFORM(MAC) |
| |
| @interface NSView (WebSetNeedsDisplayInRect) |
| - (void)_web_setNeedsDisplayInRect:(NSRect)invalidRect; |
| @end |
| |
| @implementation NSView (WebSetNeedsDisplayInRect) |
| |
| - (void)_web_setNeedsDisplayInRect:(NSRect)invalidRect |
| { |
| // Note that we call method_exchangeImplementations below, so any calls |
| // to _web_setNeedsDisplayInRect: will actually call -[NSView setNeedsDisplayInRect:]. |
| |
| if (![NSThread isMainThread] || ![self _drawnByAncestor]) { |
| [self _web_setNeedsDisplayInRect:invalidRect]; |
| return; |
| } |
| |
| static Class webFrameViewClass = [WebFrameView class]; |
| WebFrameView *enclosingWebFrameView = (WebFrameView *)self; |
| while (enclosingWebFrameView && ![enclosingWebFrameView isKindOfClass:webFrameViewClass]) |
| enclosingWebFrameView = (WebFrameView *)[enclosingWebFrameView superview]; |
| |
| if (!enclosingWebFrameView) { |
| [self _web_setNeedsDisplayInRect:invalidRect]; |
| return; |
| } |
| |
| auto* coreFrame = core([enclosingWebFrameView webFrame]); |
| auto* frameView = coreFrame ? coreFrame->view() : 0; |
| if (!frameView || !frameView->isEnclosedInCompositingLayer()) { |
| [self _web_setNeedsDisplayInRect:invalidRect]; |
| return; |
| } |
| |
| NSRect invalidRectInWebFrameViewCoordinates = [enclosingWebFrameView convertRect:invalidRect fromView:self]; |
| WebCore::IntRect invalidRectInFrameViewCoordinates(invalidRectInWebFrameViewCoordinates); |
| if (![enclosingWebFrameView isFlipped]) |
| invalidRectInFrameViewCoordinates.setY(frameView->frameRect().size().height() - invalidRectInFrameViewCoordinates.maxY()); |
| |
| frameView->invalidateRect(invalidRectInFrameViewCoordinates); |
| } |
| |
| @end |
| |
| #endif // PLATFORM(MAC) |
| |
| const float _WebHTMLViewPrintingMinimumShrinkFactor = WebCore::PrintContext::minimumShrinkFactor(); |
| const float _WebHTMLViewPrintingMaximumShrinkFactor = WebCore::PrintContext::maximumShrinkFactor(); |
| |
| // Any non-zero value will do, but using something recognizable might help us debug some day. |
| #define TRACKING_RECT_TAG 0xBADFACE |
| |
| // FIXME: From AppKit's _NXSmartPaste constant. Get with an SPI header instead? |
| #define WebSmartPastePboardType @"NeXT smart paste pasteboard type" |
| |
| #define STANDARD_WEIGHT 5 |
| #define MIN_BOLD_WEIGHT 7 |
| #define STANDARD_BOLD_WEIGHT 9 |
| |
| #if PLATFORM(MAC) |
| |
| // <rdar://problem/4985524> References to WebCoreScrollView as a subview of a WebHTMLView may be present |
| // in some NIB files, so NSUnarchiver must be still able to look up this now-unused class. |
| @interface WebCoreScrollView : NSScrollView |
| @end |
| |
| @implementation WebCoreScrollView |
| @end |
| |
| // We need this to be able to safely reference the CachedImage for the promised drag data |
| static WebCore::CachedImageClient& promisedDataClient() |
| { |
| static NeverDestroyed<WebCore::CachedImageClient> staticCachedResourceClient; |
| return staticCachedResourceClient.get(); |
| } |
| |
| #endif |
| |
| #if PLATFORM(IOS_FAMILY) |
| static NSString * const WebMarkedTextUpdatedNotification = @"WebMarkedTextUpdated"; |
| |
| static void hardwareKeyboardAvailabilityChangedCallback(CFNotificationCenterRef, void* observer, CFStringRef, const void*, CFDictionaryRef) |
| { |
| ASSERT(observer); |
| WeakObjCPtr<WebHTMLView> weakWebView { (__bridge WebHTMLView *)observer }; |
| WebThreadRun(^{ |
| if (auto webView = weakWebView.get()) { |
| if (auto* coreFrame = core([webView _frame])) |
| coreFrame->eventHandler().capsLockStateMayHaveChanged(); |
| } |
| }); |
| } |
| #endif |
| |
| @interface WebHTMLView (WebHTMLViewFileInternal) |
| #if PLATFORM(MAC) |
| - (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard inContext:(DOMRange *)context allowPlainText:(BOOL)allowPlainText; |
| - (NSString *)_plainTextFromPasteboard:(NSPasteboard *)pasteboard; |
| - (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText; |
| - (void)_pasteAsPlainTextWithPasteboard:(NSPasteboard *)pasteboard; |
| - (void)_postFakeMouseMovedEventForFlagsChangedEvent:(NSEvent *)flagsChangedEvent; |
| - (void)_removeSuperviewObservers; |
| - (void)_removeWindowObservers; |
| #endif |
| - (BOOL)_shouldInsertFragment:(DOMDocumentFragment *)fragment replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action; |
| - (BOOL)_shouldInsertText:(NSString *)text replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action; |
| - (BOOL)_shouldReplaceSelectionWithText:(NSString *)text givenAction:(WebViewInsertAction)action; |
| - (DOMRange *)_selectedRange; |
| #if PLATFORM(MAC) |
| - (void)_writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard cachedAttributedString:(NSAttributedString *)attributedString; |
| #endif |
| - (DOMRange *)_documentRange; |
| - (void)_setMouseDownEvent:(WebEvent *)event; |
| - (WebHTMLView *)_topHTMLView; |
| - (BOOL)_isTopHTMLView; |
| #if PLATFORM(MAC) |
| - (void)_web_setPrintingModeRecursive; |
| - (void)_web_setPrintingModeRecursiveAndAdjustViewSize; |
| - (void)_web_clearPrintingModeRecursive; |
| #endif |
| @end |
| |
| #if PLATFORM(MAC) |
| |
| @interface WebHTMLView (WebHTMLViewTextCheckingInternal) |
| - (void)orderFrontSubstitutionsPanel:(id)sender; |
| - (BOOL)smartInsertDeleteEnabled; |
| - (void)setSmartInsertDeleteEnabled:(BOOL)flag; |
| - (void)toggleSmartInsertDelete:(id)sender; |
| - (BOOL)isAutomaticQuoteSubstitutionEnabled; |
| - (void)setAutomaticQuoteSubstitutionEnabled:(BOOL)flag; |
| - (void)toggleAutomaticQuoteSubstitution:(id)sender; |
| - (BOOL)isAutomaticLinkDetectionEnabled; |
| - (void)setAutomaticLinkDetectionEnabled:(BOOL)flag; |
| - (void)toggleAutomaticLinkDetection:(id)sender; |
| - (BOOL)isAutomaticDashSubstitutionEnabled; |
| - (void)setAutomaticDashSubstitutionEnabled:(BOOL)flag; |
| - (void)toggleAutomaticDashSubstitution:(id)sender; |
| - (BOOL)isAutomaticTextReplacementEnabled; |
| - (void)setAutomaticTextReplacementEnabled:(BOOL)flag; |
| - (void)toggleAutomaticTextReplacement:(id)sender; |
| - (BOOL)isAutomaticSpellingCorrectionEnabled; |
| - (void)setAutomaticSpellingCorrectionEnabled:(BOOL)flag; |
| - (void)toggleAutomaticSpellingCorrection:(id)sender; |
| @end |
| |
| #endif |
| |
| @interface WebHTMLView (WebForwardDeclaration) // FIXME: Put this in the WebFileInternal category instead of doing the forward declaration trick. |
| - (void)_setPrinting:(BOOL)printing minimumPageLogicalWidth:(float)minPageWidth logicalHeight:(float)minPageHeight originalPageWidth:(float)pageLogicalWidth originalPageHeight:(float)pageLogicalHeight maximumShrinkRatio:(float)maximumShrinkRatio adjustViewSize:(BOOL)adjustViewSize paginateScreenContent:(BOOL)paginateScreenContent; |
| @end |
| |
| #if PLATFORM(MAC) |
| @interface WebHTMLView (WebNSTextInputSupport) <NSTextInput> |
| #else |
| @interface WebHTMLView (WebNSTextInputSupport) |
| #endif |
| #if PLATFORM(MAC) |
| - (void)_updateSecureInputState; |
| - (void)_updateSelectionForInputManager; |
| #endif |
| #if PLATFORM(IOS_FAMILY) |
| - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange; |
| - (void)doCommandBySelector:(SEL)selector; |
| #endif |
| @end |
| |
| @interface NSView (WebHTMLViewFileInternal) |
| - (void)_web_addDescendentWebHTMLViewsToArray:(NSMutableArray *) array; |
| @end |
| |
| struct WebHTMLViewInterpretKeyEventsParameters { |
| WebCore::KeyboardEvent* event; |
| bool eventInterpretationHadSideEffects; |
| bool shouldSaveCommands; |
| bool consumedByIM; |
| bool executingSavedKeypressCommands; |
| }; |
| |
| @interface WebHTMLViewPrivate : NSObject { |
| @public |
| BOOL closed; |
| BOOL ignoringMouseDraggedEvents; |
| BOOL printing; |
| BOOL paginateScreenContent; |
| |
| #if PLATFORM(MAC) |
| BOOL observingSuperviewNotifications; |
| BOOL observingWindowNotifications; |
| |
| id savedSubviews; |
| BOOL subviewsSetAside; |
| #endif |
| |
| NSView *layerHostingView; |
| |
| #if PLATFORM(MAC) |
| BOOL drawingIntoLayer; |
| BOOL drawingIntoAcceleratedLayer; |
| #endif |
| |
| RetainPtr<WebEvent> mouseDownEvent; // Kept after handling the event. |
| BOOL handlingMouseDownEvent; |
| RetainPtr<WebEvent> keyDownEvent; // Kept after handling the event. |
| |
| // A WebHTMLView has a single input context, but we return nil when in non-editable content to avoid making input methods do their work. |
| // This state is saved each time selection changes, because computing it causes style recalc, which is not always safe to do. |
| BOOL exposeInputContext; |
| |
| #if PLATFORM(MAC) |
| // Track whether the view has set a secure input state. |
| BOOL isInSecureInputState; |
| |
| BOOL _forceUpdateSecureInputState; |
| #endif |
| |
| NSPoint lastScrollPosition; |
| BOOL inScrollPositionChanged; |
| |
| RetainPtr<WebPluginController> pluginController; |
| |
| #if PLATFORM(MAC) |
| RetainPtr<NSString> toolTip; |
| NSToolTipTag lastToolTipTag; |
| |
| WeakObjCPtr<id> trackingRectOwner; |
| void* trackingRectUserData; |
| |
| RetainPtr<NSTimer> autoscrollTimer; |
| RetainPtr<NSEvent> autoscrollTriggerEvent; |
| #endif |
| |
| RetainPtr<NSArray> pageRects; |
| |
| #if PLATFORM(MAC) |
| RetainPtr<WebTextCompletionController> completionController; |
| |
| BOOL transparentBackground; |
| #endif |
| |
| WebHTMLViewInterpretKeyEventsParameters* interpretKeyEventsParameters; |
| |
| RetainPtr<WebDataSource> dataSource; |
| |
| #if PLATFORM(MAC) |
| NakedPtr<WebCore::CachedImage> promisedDragTIFFDataSource; |
| #endif |
| |
| SEL selectorForDoCommandBySelector; |
| |
| #if PLATFORM(MAC) |
| BOOL installedTrackingArea; |
| id flagsChangedEventMonitor; |
| NSRange softSpaceRange; |
| #endif |
| |
| #if ENABLE(SERVICE_CONTROLS) |
| RetainPtr<WebSharingServicePickerController> currentSharingServicePickerController; |
| #endif |
| } |
| - (void)clear; |
| @end |
| |
| #if PLATFORM(MAC) |
| |
| static NSControlStateValue kit(TriState state) |
| { |
| switch (state) { |
| case TriState::False: |
| return NSControlStateValueOff; |
| case TriState::True: |
| return NSControlStateValueOn; |
| case TriState::Indeterminate: |
| return NSControlStateValueMixed; |
| } |
| ASSERT_NOT_REACHED(); |
| return NSControlStateValueOff; |
| } |
| |
| #endif |
| |
| @implementation WebHTMLViewPrivate |
| |
| #if PLATFORM(MAC) |
| |
| + (void)initialize |
| { |
| // FIXME: Shouldn't all of this move into +[WebHTMLView initialize]? |
| // And some of this work is likely redundant since +[WebHTMLView initialize] is guaranteed to run first. |
| |
| JSC::initialize(); |
| WTF::initializeMainThread(); |
| WebCore::populateJITOperations(); |
| |
| if (!oldSetCursorForMouseLocationIMP) { |
| Method setCursorMethod = class_getInstanceMethod([NSWindow class], @selector(_setCursorForMouseLocation:)); |
| ASSERT(setCursorMethod); |
| oldSetCursorForMouseLocationIMP = method_setImplementation(setCursorMethod, (IMP)setCursor); |
| ASSERT(oldSetCursorForMouseLocationIMP); |
| } |
| |
| method_exchangeImplementations(class_getInstanceMethod([NSView class], @selector(setNeedsDisplayInRect:)), class_getInstanceMethod([NSView class], @selector(_web_setNeedsDisplayInRect:))); |
| } |
| |
| #endif |
| |
| - (void)dealloc |
| { |
| if (WebCoreObjCScheduleDeallocateOnMainThread([WebHTMLViewPrivate class], self)) |
| return; |
| |
| #if PLATFORM(MAC) |
| ASSERT(!autoscrollTimer); |
| ASSERT(!autoscrollTriggerEvent); |
| #endif |
| |
| #if PLATFORM(MAC) |
| if (promisedDragTIFFDataSource) |
| promisedDragTIFFDataSource->removeClient(promisedDataClient()); |
| |
| if (flagsChangedEventMonitor) { |
| [NSEvent removeMonitor:flagsChangedEventMonitor]; |
| flagsChangedEventMonitor = nil; |
| } |
| #endif |
| |
| [super dealloc]; |
| } |
| |
| - (void)clear |
| { |
| #if PLATFORM(MAC) |
| if (promisedDragTIFFDataSource) |
| promisedDragTIFFDataSource->removeClient(promisedDataClient()); |
| #endif |
| |
| mouseDownEvent = nil; |
| keyDownEvent = nil; |
| pluginController = nil; |
| #if PLATFORM(MAC) |
| toolTip = nil; |
| completionController = nil; |
| #endif |
| dataSource = nil; |
| #if PLATFORM(MAC) |
| promisedDragTIFFDataSource = nullptr; |
| #endif |
| |
| layerHostingView = nil; |
| } |
| |
| @end |
| |
| @implementation WebHTMLView (WebHTMLViewFileInternal) |
| |
| - (DOMRange *)_documentRange |
| { |
| return [[[self _frame] DOMDocument] _documentRange]; |
| } |
| |
| - (WebDataSource *)_dataSource |
| { |
| return _private->dataSource.get(); |
| } |
| |
| - (WebView *)_webView |
| { |
| return [_private->dataSource _webView]; |
| } |
| |
| - (WebFrameView *)_frameView |
| { |
| return [[_private->dataSource webFrame] frameView]; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (DOMDocumentFragment *)_documentFragmentWithPaths:(NSArray *)paths |
| { |
| auto textNodes = adoptNS([[NSMutableArray alloc] init]); |
| |
| for (NSString *path in paths) { |
| // Non-image file types; _web_userVisibleString is appropriate here because this will |
| // be pasted as visible text. |
| NSString *url = [[[NSURL fileURLWithPath:path] _webkit_canonicalize] _web_userVisibleString]; |
| [textNodes addObject:[[[self _frame] DOMDocument] createTextNode:url]]; |
| } |
| |
| DOMDocumentFragment *fragment = [[self _frame] _documentFragmentWithNodesAsParagraphs:textNodes.get()]; |
| return [fragment firstChild] != nil ? fragment : nil; |
| } |
| |
| + (NSArray *)_excludedElementsForAttributedStringConversion |
| { |
| auto elements = adoptNS([[NSMutableArray alloc] initWithObjects: |
| // Omit style since we want style to be inline so the fragment can be easily inserted. |
| @"style", |
| // Omit xml so the result is not XHTML. |
| @"xml", |
| // Omit tags that will get stripped when converted to a fragment anyway. |
| @"doctype", @"html", @"head", @"body", |
| // Omit deprecated tags. |
| @"applet", @"basefont", @"center", @"dir", @"font", @"menu", @"s", @"strike", @"u", |
| // Omit object so no file attachments are part of the fragment. |
| #if !ENABLE(ATTACHMENT_ELEMENT) |
| // Omit object so no file attachments are part of the fragment. |
| @"object", |
| #endif |
| nil]); |
| |
| #if ENABLE(ATTACHMENT_ELEMENT) |
| if (!WebCore::RuntimeEnabledFeatures::sharedFeatures().attachmentElementEnabled()) |
| [elements addObject:@"object"]; |
| #endif |
| |
| return elements.autorelease(); |
| } |
| |
| - (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard inContext:(DOMRange *)context allowPlainText:(BOOL)allowPlainText |
| { |
| using namespace WebCore; |
| NSArray *types = [pasteboard types]; |
| DOMDocumentFragment *fragment = nil; |
| |
| if ([types containsObject:WebArchivePboardType] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:WebArchivePboardType inContext:context subresources:0])) |
| return fragment; |
| |
| if ([types containsObject:WebCore::legacyFilenamesPasteboardType()] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:WebCore::legacyFilenamesPasteboardType() inContext:context subresources:0])) |
| return fragment; |
| |
| if ([types containsObject:WebCore::legacyHTMLPasteboardType()] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:WebCore::legacyHTMLPasteboardType() inContext:context subresources:0])) |
| return fragment; |
| |
| if ([types containsObject:WebCore::legacyRTFDPasteboardType()] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:WebCore::legacyRTFDPasteboardType() inContext:context subresources:0])) |
| return fragment; |
| |
| if ([types containsObject:WebCore::legacyRTFPasteboardType()] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:WebCore::legacyRTFPasteboardType() inContext:context subresources:0])) |
| return fragment; |
| |
| if ([types containsObject:WebCore::legacyTIFFPasteboardType()] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:WebCore::legacyTIFFPasteboardType() inContext:context subresources:0])) |
| return fragment; |
| |
| if ([types containsObject:WebCore::legacyPDFPasteboardType()] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:WebCore::legacyPDFPasteboardType() inContext:context subresources:0])) |
| return fragment; |
| |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| if ([types containsObject:(NSString *)kUTTypePNG] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:(NSString *)kUTTypePNG inContext:context subresources:0])) |
| return fragment; |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| |
| if ([types containsObject:WebCore::legacyURLPasteboardType()] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:WebCore::legacyURLPasteboardType() inContext:context subresources:0])) |
| return fragment; |
| |
| if (allowPlainText && [types containsObject:WebCore::legacyStringPasteboardType()] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:WebCore::legacyStringPasteboardType() inContext:context subresources:0])) |
| return fragment; |
| |
| return nil; |
| } |
| |
| - (NSString *)_plainTextFromPasteboard:(NSPasteboard *)pasteboard |
| { |
| NSArray *types = [pasteboard types]; |
| |
| if ([types containsObject:WebCore::legacyStringPasteboardType()]) |
| return [[pasteboard stringForType:WebCore::legacyStringPasteboardType()] precomposedStringWithCanonicalMapping]; |
| |
| RetainPtr<NSAttributedString> attributedString; |
| if ([types containsObject:WebCore::legacyRTFDPasteboardType()]) |
| attributedString = adoptNS([[NSAttributedString alloc] initWithRTFD:[pasteboard dataForType:WebCore::legacyRTFDPasteboardType()] documentAttributes:NULL]); |
| if (attributedString == nil && [types containsObject:WebCore::legacyRTFPasteboardType()]) |
| attributedString = adoptNS([[NSAttributedString alloc] initWithRTF:[pasteboard dataForType:WebCore::legacyRTFPasteboardType()] documentAttributes:NULL]); |
| if (attributedString) |
| return adoptNS([[attributedString string] copy]).autorelease(); |
| |
| if ([types containsObject:WebCore::legacyFilenamesPasteboardType()]) { |
| if (NSString *string = [[pasteboard propertyListForType:WebCore::legacyFilenamesPasteboardType()] componentsJoinedByString:@"\n"]) |
| return string; |
| } |
| |
| if (NSURL *URL = [NSURL URLFromPasteboard:pasteboard]) { |
| NSString *string = [URL _web_userVisibleString]; |
| if ([string length]) |
| return string; |
| } |
| |
| return nil; |
| } |
| |
| - (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText |
| { |
| auto webView = retainPtr([self _webView]); |
| [webView _setInsertionPasteboard:pasteboard]; |
| |
| DOMRange *range = [self _selectedRange]; |
| auto* coreFrame = core([self _frame]); |
| |
| DOMDocumentFragment *fragment = [self _documentFragmentFromPasteboard:pasteboard inContext:range allowPlainText:allowPlainText]; |
| if (fragment && [self _shouldInsertFragment:fragment replacingDOMRange:range givenAction:WebViewInsertActionPasted]) |
| coreFrame->editor().pasteAsFragment(*core(fragment), [self _canSmartReplaceWithPasteboard:pasteboard], false); |
| |
| [webView _setInsertionPasteboard:nil]; |
| } |
| |
| - (void)_pasteAsPlainTextWithPasteboard:(NSPasteboard *)pasteboard |
| { |
| auto webView = retainPtr([self _webView]); |
| [webView _setInsertionPasteboard:pasteboard]; |
| |
| NSString *text = [self _plainTextFromPasteboard:pasteboard]; |
| if ([self _shouldReplaceSelectionWithText:text givenAction:WebViewInsertActionPasted]) |
| [[self _frame] _replaceSelectionWithText:text selectReplacement:NO smartReplace:[self _canSmartReplaceWithPasteboard:pasteboard]]; |
| |
| [webView _setInsertionPasteboard:nil]; |
| } |
| |
| - (void)_postFakeMouseMovedEventForFlagsChangedEvent:(NSEvent *)flagsChangedEvent |
| { |
| NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSEventTypeMouseMoved location:flagsChangedEvent.window.mouseLocationOutsideOfEventStream |
| modifierFlags:flagsChangedEvent.modifierFlags timestamp:flagsChangedEvent.timestamp windowNumber:flagsChangedEvent.windowNumber |
| context:nullptr eventNumber:0 clickCount:0 pressure:0]; |
| [self mouseMoved:fakeEvent]; |
| } |
| |
| // This method is needed to support macOS services. |
| - (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pasteboard |
| { |
| auto* coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return NO; |
| if (coreFrame->selection().selection().isContentRichlyEditable()) |
| [self _pasteWithPasteboard:pasteboard allowPlainText:YES]; |
| else |
| [self _pasteAsPlainTextWithPasteboard:pasteboard]; |
| return YES; |
| } |
| |
| - (void)_removeSuperviewObservers |
| { |
| if (!_private || !_private->observingSuperviewNotifications) |
| return; |
| |
| NSView *superview = [self superview]; |
| if (!superview || ![self window]) |
| return; |
| |
| NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; |
| [notificationCenter removeObserver:self name:NSViewFrameDidChangeNotification object:superview]; |
| [notificationCenter removeObserver:self name:NSViewBoundsDidChangeNotification object:superview]; |
| |
| _private->observingSuperviewNotifications = false; |
| } |
| |
| - (void)_removeWindowObservers |
| { |
| if (!_private->observingWindowNotifications) |
| return; |
| |
| NSWindow *window = [self window]; |
| if (!window) |
| return; |
| |
| NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; |
| [notificationCenter removeObserver:self name:NSWindowDidBecomeKeyNotification object:nil]; |
| [notificationCenter removeObserver:self name:NSWindowDidResignKeyNotification object:nil]; |
| [notificationCenter removeObserver:self name:NSWindowWillCloseNotification object:window]; |
| |
| _private->observingWindowNotifications = false; |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| - (BOOL)_shouldInsertFragment:(DOMDocumentFragment *)fragment replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action |
| { |
| WebView *webView = [self _webView]; |
| DOMNode *child = [fragment firstChild]; |
| if ([fragment lastChild] == child && [child isKindOfClass:[DOMCharacterData class]]) |
| return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:[(DOMCharacterData *)child data] replacingDOMRange:range givenAction:action]; |
| return [[webView _editingDelegateForwarder] webView:webView shouldInsertNode:fragment replacingDOMRange:range givenAction:action]; |
| } |
| |
| - (BOOL)_shouldInsertText:(NSString *)text replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action |
| { |
| WebView *webView = [self _webView]; |
| return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:text replacingDOMRange:range givenAction:action]; |
| } |
| |
| - (BOOL)_shouldReplaceSelectionWithText:(NSString *)text givenAction:(WebViewInsertAction)action |
| { |
| return [self _shouldInsertText:text replacingDOMRange:[self _selectedRange] givenAction:action]; |
| } |
| |
| - (DOMRange *)_selectedRange |
| { |
| auto* coreFrame = core([self _frame]); |
| return coreFrame ? kit(coreFrame->selection().selection().toNormalizedRange()) : nil; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (void)_writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard cachedAttributedString:(NSAttributedString *)attributedString |
| { |
| // Put HTML on the pasteboard. |
| if ([types containsObject:WebArchivePboardType]) { |
| if (auto coreArchive = WebCore::LegacyWebArchive::createFromSelection(core([self _frame]))) { |
| if (RetainPtr<CFDataRef> data = coreArchive ? coreArchive->rawDataRepresentation() : 0) |
| [pasteboard setData:(__bridge NSData *)data.get() forType:WebArchivePboardType]; |
| } |
| } |
| |
| // Put the attributed string on the pasteboard (RTF/RTFD format). |
| if ([types containsObject:WebCore::legacyRTFDPasteboardType()]) { |
| if (attributedString == nil) { |
| attributedString = [self selectedAttributedString]; |
| } |
| NSData *RTFDData = [attributedString RTFDFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:@{ }]; |
| [pasteboard setData:RTFDData forType:WebCore::legacyRTFDPasteboardType()]; |
| } |
| if ([types containsObject:WebCore::legacyRTFPasteboardType()]) { |
| if (!attributedString) |
| attributedString = [self selectedAttributedString]; |
| if ([attributedString containsAttachments]) |
| attributedString = WebCore::attributedStringByStrippingAttachmentCharacters(attributedString); |
| NSData *RTFData = [attributedString RTFFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:@{ }]; |
| [pasteboard setData:RTFData forType:WebCore::legacyRTFPasteboardType()]; |
| } |
| |
| // Put plain string on the pasteboard. |
| if ([types containsObject:WebCore::legacyStringPasteboardType()]) { |
| // Map to a plain old space because this is better for source code, other browsers do it, and |
| // because HTML forces content creators and editors to use this character any time they want two spaces in a row. |
| [pasteboard setString:[[self selectedString] stringByReplacingOccurrencesOfString:@"\u00A0" withString:@" "] forType:WebCore::legacyStringPasteboardType()]; |
| } |
| |
| if ([self _canSmartCopyOrDelete] && [types containsObject:WebSmartPastePboardType]) |
| [pasteboard setData:nil forType:WebSmartPastePboardType]; |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| - (void)_setMouseDownEvent:(WebEvent *)event |
| { |
| #if PLATFORM(MAC) |
| ASSERT(!event || [event type] == NSEventTypeLeftMouseDown || [event type] == NSEventTypeRightMouseDown || [event type] == NSEventTypeOtherMouseDown); |
| #else |
| ASSERT(!event || event.type == WebEventMouseDown); |
| #endif |
| |
| _private->mouseDownEvent = event; |
| } |
| |
| - (WebHTMLView *)_topHTMLView |
| { |
| // FIXME: this can fail if the dataSource is nil, which happens when the WebView is tearing down from the window closing. |
| WebHTMLView *view = (WebHTMLView *)[[[[_private->dataSource _webView] mainFrame] frameView] documentView]; |
| ASSERT(!view || [view isKindOfClass:[WebHTMLView class]]); |
| return view; |
| } |
| |
| - (BOOL)_isTopHTMLView |
| { |
| // FIXME: this should be a cached boolean that doesn't rely on _topHTMLView since that can fail (see _topHTMLView). |
| return self == [self _topHTMLView]; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (void)_web_setPrintingModeRecursive:(BOOL)printing adjustViewSize:(BOOL)adjustViewSize |
| { |
| auto array = adoptNS([[NSMutableArray alloc] initWithObjects:self, nil]); |
| [self _web_addDescendentWebHTMLViewsToArray:array.get()]; |
| for (WebHTMLView *view in array.get()) |
| [view _setPrinting:printing minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:adjustViewSize paginateScreenContent:[self _isInScreenPaginationMode]]; |
| } |
| |
| - (void)_web_setPrintingModeRecursive |
| { |
| [self _web_setPrintingModeRecursive:YES adjustViewSize:NO]; |
| } |
| |
| - (void)_web_clearPrintingModeRecursive |
| { |
| [self _web_setPrintingModeRecursive:NO adjustViewSize:NO]; |
| } |
| |
| - (void)_web_setPrintingModeRecursiveAndAdjustViewSize |
| { |
| [self _web_setPrintingModeRecursive:YES adjustViewSize:YES]; |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| @end |
| |
| @implementation WebHTMLView (WebPrivate) |
| |
| + (NSArray *)supportedMIMETypes |
| { |
| return [WebHTMLRepresentation supportedMIMETypes]; |
| } |
| |
| + (NSArray *)supportedMediaMIMETypes |
| { |
| return [WebHTMLRepresentation supportedMediaMIMETypes]; |
| } |
| |
| + (NSArray *)supportedImageMIMETypes |
| { |
| return [WebHTMLRepresentation supportedImageMIMETypes]; |
| } |
| |
| + (NSArray *)supportedNonImageMIMETypes |
| { |
| return [WebHTMLRepresentation supportedNonImageMIMETypes]; |
| } |
| |
| + (NSArray *)unsupportedTextMIMETypes |
| { |
| return [WebHTMLRepresentation unsupportedTextMIMETypes]; |
| } |
| |
| #if PLATFORM(IOS_FAMILY) |
| |
| - (void)mouseMoved:(WebEvent *)event |
| { |
| if (auto* frame = core([self _frame])) |
| frame->eventHandler().mouseMoved(event); |
| } |
| |
| #endif |
| |
| #if PLATFORM(MAC) |
| |
| + (void)_postFlagsChangedEvent:(NSEvent *)flagsChangedEvent |
| { |
| // This is obsolete SPI needed only for older versions of Safari |
| } |
| |
| - (id)_bridge |
| { |
| // This method exists to maintain compatibility with Leopard's Dictionary.app, since it |
| // calls _bridge to get access to convertNSRangeToDOMRange: and convertDOMRangeToNSRange:. |
| // Return the WebFrame, which implements the compatibility methods. <rdar://problem/6002160> |
| return [self _frame]; |
| } |
| |
| - (void)_updateMouseoverWithFakeEvent |
| { |
| NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSEventTypeMouseMoved |
| location:[[self window] |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| convertScreenToBase:[NSEvent mouseLocation]] |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| modifierFlags:[[NSApp currentEvent] modifierFlags] |
| timestamp:[NSDate timeIntervalSinceReferenceDate] |
| windowNumber:[[self window] windowNumber] |
| context:nullptr |
| eventNumber:0 clickCount:0 pressure:0]; |
| |
| [self _updateMouseoverWithEvent:fakeEvent]; |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| - (void)_frameOrBoundsChanged |
| { |
| WebView *webView = [self _webView]; |
| WebDynamicScrollBarsView *scrollView = [[[webView mainFrame] frameView] _scrollView]; |
| |
| NSPoint origin = [[self superview] bounds].origin; |
| if (!NSEqualPoints(_private->lastScrollPosition, origin) && ![scrollView inProgrammaticScroll]) { |
| if (auto* coreFrame = core([self _frame])) { |
| if (auto* coreView = coreFrame->view()) { |
| _private->inScrollPositionChanged = YES; |
| coreView->scrollOffsetChangedViaPlatformWidget(WebCore::IntPoint(_private->lastScrollPosition), WebCore::IntPoint(origin)); |
| _private->inScrollPositionChanged = NO; |
| } |
| } |
| |
| #if PLATFORM(MAC) |
| [_private->completionController endRevertingChange:NO moveLeft:NO]; |
| #endif |
| |
| [webView _didScrollDocumentInFrameView:[self _frameView]]; |
| } |
| _private->lastScrollPosition = origin; |
| } |
| |
| - (void)_setAsideSubviews |
| { |
| #if PLATFORM(MAC) |
| ASSERT(!_private->subviewsSetAside); |
| ASSERT(_private->savedSubviews == nil); |
| _private->savedSubviews = self._subviewsIvar; |
| // We need to keep the layer-hosting view in the subviews, otherwise the layers flash. |
| if (_private->layerHostingView) { |
| NSMutableArray* newSubviews = [[NSMutableArray alloc] initWithObjects:_private->layerHostingView, nil]; |
| self._subviewsIvar = newSubviews; |
| } else |
| self._subviewsIvar = nil; |
| _private->subviewsSetAside = YES; |
| #endif |
| } |
| |
| - (void)_restoreSubviews |
| { |
| #if PLATFORM(MAC) |
| ASSERT(_private->subviewsSetAside); |
| if (_private->layerHostingView) { |
| [self._subviewsIvar release]; |
| self._subviewsIvar = _private->savedSubviews; |
| } else { |
| ASSERT(self._subviewsIvar == nil); |
| self._subviewsIvar = _private->savedSubviews; |
| } |
| _private->savedSubviews = nil; |
| _private->subviewsSetAside = NO; |
| #endif |
| } |
| |
| - (void)viewWillDraw |
| { |
| // On window close we will be called when the data source is nil, then hit an assert in _topHTMLView |
| // So check if the data source is nil before calling [self _isTopHTMLView], this can be removed |
| // once the FIXME in _isTopHTMLView is fixed. |
| if (_private->dataSource && [self _isTopHTMLView]) { |
| [self _web_updateLayoutAndStyleIfNeededRecursive]; |
| [[self _webView] _flushCompositingChanges]; |
| } |
| |
| [super viewWillDraw]; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| // Don't let AppKit even draw subviews. We take care of that. |
| - (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView |
| { |
| // This helps when we print as part of a larger print process. |
| // If the WebHTMLView itself is what we're printing, then we will never have to do this. |
| BOOL wasInPrintingMode = _private->printing; |
| BOOL isPrinting = ![NSGraphicsContext currentContextDrawingToScreen]; |
| if (isPrinting) { |
| if (!wasInPrintingMode) |
| [self _web_setPrintingModeRecursive]; |
| else |
| [self _web_updateLayoutAndStyleIfNeededRecursive]; |
| } else if (wasInPrintingMode) |
| [self _web_clearPrintingModeRecursive]; |
| |
| // There are known cases where -viewWillDraw is not called on all views being drawn. |
| // See <rdar://problem/6964278> for example. Performing layout at this point prevents us from |
| // trying to paint without layout (which WebCore now refuses to do, instead bailing out without |
| // drawing at all), but we may still fail to update any regions dirtied by the layout which are |
| // not already dirty. |
| if ([self _needsLayout]) { |
| NSInteger rectCount; |
| [self getRectsBeingDrawn:0 count:&rectCount]; |
| if (rectCount) { |
| LOG_ERROR("View needs layout. Either -viewWillDraw wasn't called or layout was invalidated during the display operation. Performing layout now."); |
| [self _web_updateLayoutAndStyleIfNeededRecursive]; |
| } |
| } |
| |
| [self _setAsideSubviews]; |
| [super _recursiveDisplayRectIfNeededIgnoringOpacity:rect isVisibleRect:isVisibleRect rectIsVisibleRectForView:visibleView topView:topView]; |
| [self _restoreSubviews]; |
| |
| if (wasInPrintingMode != isPrinting) { |
| if (wasInPrintingMode) |
| [self _web_setPrintingModeRecursive]; |
| else |
| [self _web_clearPrintingModeRecursive]; |
| } |
| } |
| |
| // Don't let AppKit even draw subviews. We take care of that. |
| - (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect |
| { |
| BOOL needToSetAsideSubviews = !_private->subviewsSetAside; |
| |
| BOOL wasInPrintingMode = _private->printing; |
| BOOL isPrinting = ![NSGraphicsContext currentContextDrawingToScreen]; |
| |
| if (needToSetAsideSubviews) { |
| // This helps when we print as part of a larger print process. |
| // If the WebHTMLView itself is what we're printing, then we will never have to do this. |
| if (isPrinting) { |
| if (!wasInPrintingMode) |
| [self _web_setPrintingModeRecursive]; |
| else |
| [self _web_updateLayoutAndStyleIfNeededRecursive]; |
| } else if (wasInPrintingMode) |
| [self _web_clearPrintingModeRecursive]; |
| |
| |
| [self _setAsideSubviews]; |
| } |
| |
| [super _recursiveDisplayAllDirtyWithLockFocus:needsLockFocus visRect:visRect]; |
| |
| if (needToSetAsideSubviews) { |
| if (wasInPrintingMode != isPrinting) { |
| if (wasInPrintingMode) |
| [self _web_setPrintingModeRecursive]; |
| else |
| [self _web_clearPrintingModeRecursive]; |
| } |
| |
| [self _restoreSubviews]; |
| } |
| } |
| |
| // Don't let AppKit even draw subviews. We take care of that. |
| - (void)_recursive:(BOOL)recursive displayRectIgnoringOpacity:(NSRect)displayRect inContext:(NSGraphicsContext *)graphicsContext stopAtLayerBackedViews:(BOOL)stopAtLayerBackedViews |
| { |
| [self _setAsideSubviews]; |
| [super _recursive:recursive displayRectIgnoringOpacity:displayRect inContext:graphicsContext stopAtLayerBackedViews:stopAtLayerBackedViews]; |
| [self _restoreSubviews]; |
| } |
| |
| static BOOL isQuickLookEvent(NSEvent *event) |
| { |
| const int kCGSEventSystemSubtypeHotKeyCombinationReleased = 9; |
| return [event type] == NSEventTypeSystemDefined && [event subtype] == kCGSEventSystemSubtypeHotKeyCombinationReleased && [event data1] == 'lkup'; |
| } |
| |
| #endif |
| |
| - (NSView *)hitTest:(NSPoint)point |
| { |
| // WebHTMLView objects handle all events for objects inside them. |
| // To get those events, we prevent hit testing from AppKit. |
| |
| // But there are three exceptions to this: |
| // 1) For right mouse clicks and control clicks we don't yet have an implementation |
| // that works for nested views, so we let the hit testing go through the |
| // standard NSView code path (needs to be fixed, see bug 4361618). |
| // 2) Java depends on doing a hit test inside it's mouse moved handling, |
| // so we let the hit testing go through the standard NSView code path |
| // when the current event is a mouse move (except when we are calling |
| // from _updateMouseoverWithEvent, so we have to use a global, |
| // forceWebHTMLViewHitTest, for that) |
| // 3) The acceptsFirstMouse: and shouldDelayWindowOrderingForEvent: methods |
| // both need to figure out which view to check with inside the WebHTMLView. |
| // They use a global to change the behavior of hitTest: so they can get the |
| // right view. The global is forceNSViewHitTest and the method they use to |
| // do the hit testing is _hitViewForEvent:. (But this does not work correctly |
| // when there is HTML overlapping the view, see bug 4361626) |
| // 4) NSAccessibilityHitTest relies on this for checking the cursor position. |
| // Our check for that is whether the event is NSFlagsChanged. This works |
| // for VoiceOver's Control-Option-F5 command (move focus to item under cursor) |
| // and Dictionary's Command-Control-D (open dictionary popup for item under cursor). |
| // This is of course a hack. |
| |
| if (_private->closed) |
| return nil; |
| |
| #if !PLATFORM(IOS_FAMILY) |
| BOOL captureHitsOnSubviews; |
| if (forceNSViewHitTest) |
| captureHitsOnSubviews = NO; |
| else if (forceWebHTMLViewHitTest) |
| captureHitsOnSubviews = YES; |
| else { |
| // FIXME: Why doesn't this include mouse entered/exited events, or other mouse button events? |
| NSEvent *event = [[self window] currentEvent]; |
| captureHitsOnSubviews = !([event type] == NSEventTypeMouseMoved |
| || [event type] == NSEventTypeRightMouseDown |
| || ([event type] == NSEventTypeLeftMouseDown && [event modifierFlags] & NSEventModifierFlagControl) |
| || [event type] == NSEventTypeFlagsChanged |
| || isQuickLookEvent(event)); |
| } |
| |
| if (!captureHitsOnSubviews) { |
| NSView* hitView = [super hitTest:point]; |
| if (_private && hitView == _private->layerHostingView) |
| hitView = self; |
| return hitView; |
| } |
| #endif // !PLATFORM(IOS_FAMILY) |
| |
| if ([[self superview] mouse:point inRect:[self frame]]) |
| return self; |
| return nil; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (NSTrackingRectTag)addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside |
| { |
| ASSERT(!_private->trackingRectOwner); |
| _private->trackingRectOwner = owner; |
| _private->trackingRectUserData = data; |
| return TRACKING_RECT_TAG; |
| } |
| |
| - (NSTrackingRectTag)_addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside useTrackingNum:(int)tag |
| { |
| ASSERT(tag == 0 || tag == TRACKING_RECT_TAG); |
| ASSERT(!_private->trackingRectOwner); |
| _private->trackingRectOwner = owner; |
| _private->trackingRectUserData = data; |
| return TRACKING_RECT_TAG; |
| } |
| |
| - (void)_addTrackingRects:(NSRect *)rects owner:(id)owner userDataList:(void **)userDataList assumeInsideList:(BOOL *)assumeInsideList trackingNums:(NSTrackingRectTag *)trackingNums count:(int)count |
| { |
| ASSERT(count == 1); |
| ASSERT(trackingNums[0] == 0 || trackingNums[0] == TRACKING_RECT_TAG); |
| ASSERT(!_private->trackingRectOwner); |
| _private->trackingRectOwner = owner; |
| _private->trackingRectUserData = userDataList[0]; |
| trackingNums[0] = TRACKING_RECT_TAG; |
| } |
| |
| - (void)removeTrackingRect:(NSTrackingRectTag)tag |
| { |
| if (tag == 0) |
| return; |
| |
| if (_private && (tag == TRACKING_RECT_TAG)) { |
| _private->trackingRectOwner = nil; |
| return; |
| } |
| |
| if (_private && (tag == _private->lastToolTipTag)) { |
| [super removeTrackingRect:tag]; |
| _private->lastToolTipTag = 0; |
| return; |
| } |
| |
| // If any other tracking rect is being removed, we don't know how it was created |
| // and it's possible there's a leak involved (see 3500217) |
| ASSERT_NOT_REACHED(); |
| } |
| |
| - (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count |
| { |
| int i; |
| for (i = 0; i < count; ++i) { |
| int tag = tags[i]; |
| if (tag == 0) |
| continue; |
| ASSERT(tag == TRACKING_RECT_TAG); |
| if (_private != nil) { |
| _private->trackingRectOwner = nil; |
| } |
| } |
| } |
| |
| - (id)_toolTipOwnerForSendingMouseEvents |
| { |
| if (id owner = _private->trackingRectOwner.getAutoreleased()) |
| return owner; |
| |
| for (NSTrackingArea *trackingArea in self.trackingAreas) { |
| static Class managerClass; |
| static std::once_flag onceFlag; |
| std::call_once(onceFlag, [] { |
| managerClass = NSClassFromString(@"NSToolTipManager"); |
| }); |
| |
| id owner = trackingArea.owner; |
| if ([owner class] == managerClass) |
| return owner; |
| } |
| return nil; |
| } |
| |
| - (void)_sendToolTipMouseExited |
| { |
| // Nothing matters except window, trackingNumber, and userData. |
| NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSEventTypeMouseExited |
| location:NSMakePoint(0, 0) |
| modifierFlags:0 |
| timestamp:0 |
| windowNumber:[[self window] windowNumber] |
| context:NULL |
| eventNumber:0 |
| trackingNumber:TRACKING_RECT_TAG |
| userData:_private->trackingRectUserData]; |
| [self._toolTipOwnerForSendingMouseEvents mouseExited:fakeEvent]; |
| } |
| |
| - (void)_sendToolTipMouseEntered |
| { |
| // Nothing matters except window, trackingNumber, and userData. |
| NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSEventTypeMouseEntered |
| location:NSMakePoint(0, 0) |
| modifierFlags:0 |
| timestamp:0 |
| windowNumber:[[self window] windowNumber] |
| context:NULL |
| eventNumber:0 |
| trackingNumber:TRACKING_RECT_TAG |
| userData:_private->trackingRectUserData]; |
| [self._toolTipOwnerForSendingMouseEvents mouseEntered:fakeEvent]; |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| - (void)_setToolTip:(NSString *)string |
| { |
| #if PLATFORM(MAC) |
| NSString *toolTip = [string length] == 0 ? nil : string; |
| NSString *oldToolTip = _private->toolTip.get(); |
| if (toolTip == oldToolTip || [toolTip isEqualToString:oldToolTip]) |
| return; |
| if (oldToolTip) |
| [self _sendToolTipMouseExited]; |
| _private->toolTip = adoptNS([toolTip copy]); |
| if (toolTip) { |
| // See radar 3500217 for why we remove all tooltips rather than just the single one we created. |
| [self removeAllToolTips]; |
| NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000); |
| _private->lastToolTipTag = [self addToolTipRect:wideOpenRect owner:self userData:NULL]; |
| [self _sendToolTipMouseEntered]; |
| } |
| #endif |
| } |
| |
| #if PLATFORM(MAC) |
| |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN |
| - (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)data |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_END |
| { |
| return adoptNS([_private->toolTip copy]).autorelease(); |
| } |
| |
| static bool mouseEventIsPartOfClickOrDrag(NSEvent *event) |
| { |
| switch ([event type]) { |
| case NSEventTypeLeftMouseDown: |
| case NSEventTypeLeftMouseUp: |
| case NSEventTypeLeftMouseDragged: |
| case NSEventTypeRightMouseDown: |
| case NSEventTypeRightMouseUp: |
| case NSEventTypeRightMouseDragged: |
| case NSEventTypeOtherMouseDown: |
| case NSEventTypeOtherMouseUp: |
| case NSEventTypeOtherMouseDragged: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| - (void)_updateMouseoverWithEvent:(NSEvent *)event |
| { |
| if (_private->closed) |
| return; |
| |
| NSView *contentView = [[event window] contentView]; |
| NSPoint locationForHitTest = [[contentView superview] convertPoint:[event locationInWindow] fromView:nil]; |
| |
| forceWebHTMLViewHitTest = YES; |
| NSView *hitView = [contentView hitTest:locationForHitTest]; |
| forceWebHTMLViewHitTest = NO; |
| |
| auto view = retainPtr(dynamic_objc_cast<WebHTMLView>(hitView)); |
| if (lastHitView != view && lastHitView && [lastHitView _frame]) { |
| // If we are moving out of a view (or frame), let's pretend the mouse moved |
| // all the way out of that view. But we have to account for scrolling, because |
| // WebCore doesn't understand our clipping. |
| NSRect visibleRect = [[[[lastHitView _frame] frameView] _scrollView] documentVisibleRect]; |
| float yScroll = visibleRect.origin.y; |
| float xScroll = visibleRect.origin.x; |
| |
| NSEvent *event = [NSEvent mouseEventWithType:NSEventTypeMouseMoved |
| location:NSMakePoint(-1 - xScroll, -1 - yScroll) |
| modifierFlags:[[NSApp currentEvent] modifierFlags] |
| timestamp:[NSDate timeIntervalSinceReferenceDate] |
| windowNumber:[[view window] windowNumber] |
| context:nullptr |
| eventNumber:0 clickCount:0 pressure:0]; |
| |
| if (auto* lastHitCoreFrame = core([lastHitView _frame])) |
| lastHitCoreFrame->eventHandler().mouseMoved(event, [[self _webView] _pressureEvent]); |
| } |
| |
| lastHitView = view.get(); |
| |
| if (view) { |
| if (auto* coreFrame = core([view _frame])) { |
| // We need to do a full, normal hit test during this mouse event if the page is active or if a mouse |
| // button is currently pressed. It is possible that neither of those things will be true on Lion and |
| // newer when legacy scrollbars are enabled, because then WebKit receives mouse events all the time. |
| // If it is one of those cases where the page is not active and the mouse is not pressed, then we can |
| // fire a much more restricted and efficient scrollbars-only version of the event. |
| |
| if ([[self window] isKeyWindow] || mouseEventIsPartOfClickOrDrag(event)) |
| coreFrame->eventHandler().mouseMoved(event, [[self _webView] _pressureEvent]); |
| else { |
| [self removeAllToolTips]; |
| coreFrame->eventHandler().passMouseMovedEventToScrollbars(event, [[self _webView] _pressureEvent]); |
| } |
| } |
| } |
| } |
| |
| + (NSString *)_dummyPasteboardType |
| { |
| return @"Apple WebKit dummy pasteboard type"; |
| } |
| |
| + (NSArray *)_insertablePasteboardTypes |
| { |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| static NeverDestroyed<RetainPtr<NSArray>> types = @[ |
| WebArchivePboardType, WebCore::legacyHTMLPasteboardType(), WebCore::legacyFilenamesPasteboardType(), WebCore::legacyTIFFPasteboardType(), |
| WebCore::legacyPDFPasteboardType(), WebCore::legacyURLPasteboardType(), WebCore::legacyRTFDPasteboardType(), WebCore::legacyRTFPasteboardType(), |
| WebCore::legacyStringPasteboardType(), WebCore::legacyColorPasteboardType(), (NSString *)kUTTypePNG, |
| ]; |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| return types.get().get(); |
| } |
| |
| + (NSArray *)_selectionPasteboardTypes |
| { |
| // FIXME: We should put data for NSHTMLPboardType on the pasteboard but Microsoft Excel doesn't like our format of HTML (3640423). |
| return @[WebArchivePboardType, WebCore::legacyRTFDPasteboardType(), WebCore::legacyRTFPasteboardType(), WebCore::legacyStringPasteboardType()]; |
| } |
| |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN |
| - (void)pasteboardChangedOwner:(NSPasteboard *)pasteboard |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_END |
| { |
| [self setPromisedDragTIFFDataSource:nullptr]; |
| } |
| |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN |
| - (void)pasteboard:(NSPasteboard *)pasteboard provideDataForType:(NSString *)type |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_END |
| { |
| if ([type isEqualToString:WebCore::legacyRTFDPasteboardType()] && [[pasteboard types] containsObject:WebArchivePboardType]) { |
| auto archive = adoptNS([[WebArchive alloc] initWithData:[pasteboard dataForType:WebArchivePboardType]]); |
| [pasteboard _web_writePromisedRTFDFromArchive:archive.get() containsImage:[[pasteboard types] containsObject:WebCore::legacyTIFFPasteboardType()]]; |
| } else if ([type isEqualToString:WebCore::legacyTIFFPasteboardType()] && _private->promisedDragTIFFDataSource) { |
| if (auto* image = _private->promisedDragTIFFDataSource->image()) |
| [pasteboard setData:(__bridge NSData *)image->tiffRepresentation() forType:WebCore::legacyTIFFPasteboardType()]; |
| [self setPromisedDragTIFFDataSource:nullptr]; |
| } |
| } |
| |
| - (void)_handleAutoscrollForMouseDragged:(NSEvent *)event |
| { |
| [self autoscroll:event]; |
| [self _startAutoscrollTimer:event]; |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| #if PLATFORM(IOS_FAMILY) |
| |
| // WAKView override. |
| - (void)layoutIfNeeded |
| { |
| [self _layoutIfNeeded]; |
| } |
| |
| // WAKView override. |
| - (void)setScale:(float)scale |
| { |
| [super setScale:scale]; |
| auto* coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return; |
| |
| if (auto* page = coreFrame->page()) |
| page->setPageScaleFactor(scale, WebCore::IntPoint()); |
| |
| [[self _webView] _documentScaleChanged]; |
| } |
| |
| #endif |
| |
| #if PLATFORM(MAC) |
| |
| - (void)_layoutForPrinting |
| { |
| // Set printing mode temporarily so we can adjust the size of the view. This will allow |
| // AppKit's pagination code to use the correct height for the page content. Leaving printing |
| // mode on indefinitely would interfere with Mail's printing mechanism (at least), so we just |
| // turn it off again after adjusting the size. |
| [self _web_setPrintingModeRecursiveAndAdjustViewSize]; |
| [self _web_clearPrintingModeRecursive]; |
| } |
| |
| - (void)_smartInsertForString:(NSString *)pasteString replacingRange:(DOMRange *)rangeToReplace beforeString:(NSString **)beforeString afterString:(NSString **)afterString |
| { |
| if (!pasteString || !rangeToReplace || ![[self _webView] smartInsertDeleteEnabled]) { |
| if (beforeString) |
| *beforeString = nil; |
| if (afterString) |
| *afterString = nil; |
| return; |
| } |
| |
| [[self _frame] _smartInsertForString:pasteString replacingRange:rangeToReplace beforeString:beforeString afterString:afterString]; |
| } |
| |
| - (BOOL)_canSmartReplaceWithPasteboard:(NSPasteboard *)pasteboard |
| { |
| return [[self _webView] smartInsertDeleteEnabled] && [[pasteboard types] containsObject:WebSmartPastePboardType]; |
| } |
| |
| #endif |
| |
| // FIXME: _selectionRect is deprecated in favor of selectionRect, which is in protocol WebDocumentSelection. |
| // We can't remove this yet because it's still in use by Mail. |
| - (NSRect)_selectionRect |
| { |
| return [self selectionRect]; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (void)_autoscroll |
| { |
| // Guarantee that the autoscroll timer is invalidated, even if we don't receive |
| // a mouse up event. |
| BOOL isStillDown = CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kCGMouseButtonLeft); |
| if (!isStillDown){ |
| [self _stopAutoscrollTimer]; |
| return; |
| } |
| |
| NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDragged |
| location:[[self window] |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| convertScreenToBase:[NSEvent mouseLocation]] |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| modifierFlags:[[NSApp currentEvent] modifierFlags] |
| timestamp:[NSDate timeIntervalSinceReferenceDate] |
| windowNumber:[[self window] windowNumber] |
| context:nullptr |
| eventNumber:0 clickCount:0 pressure:0]; |
| |
| [self mouseDragged:fakeEvent]; |
| } |
| |
| #endif |
| |
| - (BOOL)_canEdit |
| { |
| auto* coreFrame = core([self _frame]); |
| return coreFrame && coreFrame->editor().canEdit(); |
| } |
| |
| - (BOOL)_canEditRichly |
| { |
| auto* coreFrame = core([self _frame]); |
| return coreFrame && coreFrame->editor().canEditRichly(); |
| } |
| |
| - (BOOL)_canAlterCurrentSelection |
| { |
| return [self _hasSelectionOrInsertionPoint] && [self _isEditable]; |
| } |
| |
| - (BOOL)_hasSelection |
| { |
| auto* coreFrame = core([self _frame]); |
| return coreFrame && coreFrame->selection().selection().isRange(); |
| } |
| |
| - (BOOL)_hasSelectionOrInsertionPoint |
| { |
| auto* coreFrame = core([self _frame]); |
| return coreFrame && coreFrame->selection().selection().isCaretOrRange(); |
| } |
| |
| - (BOOL)_hasInsertionPoint |
| { |
| auto* coreFrame = core([self _frame]); |
| return coreFrame && coreFrame->selection().selection().isCaret(); |
| } |
| |
| - (BOOL)_isEditable |
| { |
| auto* coreFrame = core([self _frame]); |
| return coreFrame && coreFrame->selection().selection().isContentEditable(); |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (BOOL)_transparentBackground |
| { |
| return _private->transparentBackground; |
| } |
| |
| - (void)_setTransparentBackground:(BOOL)f |
| { |
| _private->transparentBackground = f; |
| } |
| |
| - (NSImage *)_selectionDraggingImage |
| { |
| if (![self _hasSelection]) |
| return nil; |
| |
| auto* coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return nil; |
| |
| Ref<WebCore::Frame> protectedCoreFrame(*coreFrame); |
| |
| WebCore::TextIndicatorData textIndicator; |
| auto dragImage = createDragImageForSelection(*coreFrame, textIndicator); |
| [dragImage _web_dissolveToFraction:WebDragImageAlpha]; |
| |
| return dragImage.autorelease(); |
| } |
| |
| - (NSRect)_selectionDraggingRect |
| { |
| // Mail currently calls this method. We can eliminate it when Mail no longer calls it. |
| return [self selectionRect]; |
| } |
| |
| #endif |
| |
| - (DOMNode *)_insertOrderedList |
| { |
| auto* coreFrame = core([self _frame]); |
| return coreFrame ? kit(coreFrame->editor().insertOrderedList().get()) : nil; |
| } |
| |
| - (DOMNode *)_insertUnorderedList |
| { |
| auto* coreFrame = core([self _frame]); |
| return coreFrame ? kit(coreFrame->editor().insertUnorderedList().get()) : nil; |
| } |
| |
| - (BOOL)_canIncreaseSelectionListLevel |
| { |
| auto* coreFrame = core([self _frame]); |
| return coreFrame && coreFrame->editor().canIncreaseSelectionListLevel(); |
| } |
| |
| - (BOOL)_canDecreaseSelectionListLevel |
| { |
| auto* coreFrame = core([self _frame]); |
| return coreFrame && coreFrame->editor().canDecreaseSelectionListLevel(); |
| } |
| |
| - (DOMNode *)_increaseSelectionListLevel |
| { |
| auto* coreFrame = core([self _frame]); |
| return coreFrame ? kit(coreFrame->editor().increaseSelectionListLevel().get()) : nil; |
| } |
| |
| - (DOMNode *)_increaseSelectionListLevelOrdered |
| { |
| auto* coreFrame = core([self _frame]); |
| return coreFrame ? kit(coreFrame->editor().increaseSelectionListLevelOrdered().get()) : nil; |
| } |
| |
| - (DOMNode *)_increaseSelectionListLevelUnordered |
| { |
| auto* coreFrame = core([self _frame]); |
| return coreFrame ? kit(coreFrame->editor().increaseSelectionListLevelUnordered().get()) : nil; |
| } |
| |
| - (void)_decreaseSelectionListLevel |
| { |
| auto* coreFrame = core([self _frame]); |
| if (coreFrame) |
| coreFrame->editor().decreaseSelectionListLevel(); |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (void)_writeSelectionToPasteboard:(NSPasteboard *)pasteboard |
| { |
| ASSERT([self _hasSelection]); |
| NSArray *types = [self pasteboardTypesForSelection]; |
| |
| // Don't write RTFD to the pasteboard when the copied attributed string has no attachments. |
| NSAttributedString *attributedString = [self selectedAttributedString]; |
| RetainPtr<NSMutableArray> mutableTypes; |
| if (![attributedString containsAttachments]) { |
| mutableTypes = adoptNS([types mutableCopy]); |
| [mutableTypes removeObject:WebCore::legacyRTFDPasteboardType()]; |
| types = mutableTypes.get(); |
| } |
| |
| [pasteboard declareTypes:types owner:[self _topHTMLView]]; |
| [self _writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard cachedAttributedString:attributedString]; |
| } |
| |
| #endif |
| |
| - (void)close |
| { |
| // Check for a nil _private here in case we were created with initWithCoder. In that case, the WebView is just throwing |
| // out the archived WebHTMLView and recreating a new one if needed. So close doesn't need to do anything in that case. |
| if (!_private || _private->closed) |
| return; |
| |
| _private->closed = YES; |
| |
| #if PLATFORM(MAC) |
| if (lastHitView == self) |
| lastHitView = nil; |
| |
| [self _removeWindowObservers]; |
| [self _removeSuperviewObservers]; |
| #endif |
| |
| [_private->pluginController destroyAllPlugins]; |
| [_private->pluginController setDataSource:nil]; |
| |
| #if PLATFORM(MAC) |
| // remove tooltips before clearing _private so removeTrackingRect: will work correctly |
| [self removeAllToolTips]; |
| |
| if (_private->isInSecureInputState) { |
| DisableSecureEventInput(); |
| _private->isInSecureInputState = NO; |
| } |
| #endif |
| |
| [_private clear]; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (DOMDocumentFragment *)_web_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard pasteboardType:(NSString *)pasteboardType imageMIMEType:(NSString *)imageMIMEType |
| { |
| String filename = [imageMIMEType stringByReplacingOccurrencesOfString:@"/" withString:@"."]; |
| auto resource = adoptNS([[WebResource alloc] initWithData:[pasteboard dataForType:pasteboardType] |
| URL:URL::fakeURLWithRelativePart(filename) MIMEType:imageMIMEType textEncodingName:nil frameName:nil]); |
| return [[self _dataSource] _documentFragmentWithImageResource:resource.get()]; |
| } |
| |
| - (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard |
| forType:(NSString *)pboardType |
| inContext:(DOMRange *)context |
| subresources:(NSArray **)subresources |
| { |
| if ([pboardType isEqualToString:WebArchivePboardType]) { |
| auto archive = adoptNS([[WebArchive alloc] initWithData:[pasteboard dataForType:WebArchivePboardType]]); |
| if (subresources) |
| *subresources = [archive subresources]; |
| return [[self _dataSource] _documentFragmentWithArchive:archive.get()]; |
| } |
| |
| if ([pboardType isEqualToString:WebCore::legacyFilenamesPasteboardType()]) |
| return [self _documentFragmentWithPaths:[pasteboard propertyListForType:WebCore::legacyFilenamesPasteboardType()]]; |
| |
| if ([pboardType isEqualToString:WebCore::legacyHTMLPasteboardType()]) { |
| NSString *HTMLString = [pasteboard stringForType:WebCore::legacyHTMLPasteboardType()]; |
| // This is a hack to make Microsoft's HTML pasteboard data work. See 3778785. |
| if ([HTMLString hasPrefix:@"Version:"]) { |
| NSRange range = [HTMLString rangeOfString:@"<html" options:NSCaseInsensitiveSearch]; |
| if (range.location != NSNotFound) |
| HTMLString = [HTMLString substringFromIndex:range.location]; |
| } |
| if ([HTMLString length] == 0) |
| return nil; |
| return [[self _frame] _documentFragmentWithMarkupString:HTMLString baseURLString:nil]; |
| } |
| |
| if ([pboardType isEqualToString:WebCore::legacyRTFPasteboardType()] || [pboardType isEqualToString:WebCore::legacyRTFDPasteboardType()]) { |
| RetainPtr<NSAttributedString> string; |
| if ([pboardType isEqualToString:WebCore::legacyRTFDPasteboardType()]) |
| string = adoptNS([[NSAttributedString alloc] initWithRTFD:[pasteboard dataForType:WebCore::legacyRTFDPasteboardType()] documentAttributes:NULL]); |
| if (!string) |
| string = adoptNS([[NSAttributedString alloc] initWithRTF:[pasteboard dataForType:WebCore::legacyRTFPasteboardType()] documentAttributes:NULL]); |
| if (!string) |
| return nil; |
| |
| auto documentAttributes = adoptNS([[NSDictionary alloc] initWithObjectsAndKeys: |
| [[self class] _excludedElementsForAttributedStringConversion], NSExcludedElementsDocumentAttribute, |
| nil]); |
| |
| BOOL wasDeferringCallbacks = [[self _webView] defersCallbacks]; |
| if (!wasDeferringCallbacks) |
| [[self _webView] setDefersCallbacks:YES]; |
| |
| NSArray *localSubresources; |
| DOMDocumentFragment *fragment = [string _documentFromRange:NSMakeRange(0, [string length]) document:[[self _frame] DOMDocument] |
| documentAttributes:documentAttributes.get() subresources:&localSubresources]; |
| |
| if (subresources) |
| *subresources = localSubresources; |
| |
| for (WebResource *resource in localSubresources) |
| [[self _dataSource] addSubresource:resource]; |
| |
| if (!wasDeferringCallbacks) |
| [[self _webView] setDefersCallbacks:NO]; |
| |
| return fragment; |
| } |
| |
| if ([pboardType isEqualToString:WebCore::legacyTIFFPasteboardType()]) |
| return [self _web_documentFragmentFromPasteboard:pasteboard pasteboardType:WebCore::legacyTIFFPasteboardType() imageMIMEType:@"image/tiff"]; |
| if ([pboardType isEqualToString:WebCore::legacyPDFPasteboardType()]) |
| return [self _web_documentFragmentFromPasteboard:pasteboard pasteboardType:WebCore::legacyPDFPasteboardType() imageMIMEType:@"application/pdf"]; |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| if ([pboardType isEqualToString:(NSString *)kUTTypePNG]) |
| return [self _web_documentFragmentFromPasteboard:pasteboard pasteboardType:(NSString *)kUTTypePNG imageMIMEType:@"image/png"]; |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| |
| if ([pboardType isEqualToString:WebCore::legacyURLPasteboardType()]) { |
| NSURL *URL = [NSURL URLFromPasteboard:pasteboard]; |
| DOMDocument* document = [[self _frame] DOMDocument]; |
| ASSERT(document); |
| if (!document) |
| return nil; |
| DOMHTMLAnchorElement *anchor = (DOMHTMLAnchorElement *)[document createElement:@"a"]; |
| NSString *URLString = [URL _web_originalDataAsString]; // Original data is ASCII-only, so there is no need to precompose. |
| if ([URLString length] == 0) |
| return nil; |
| NSString *URLTitleString = [[pasteboard stringForType:WebURLNamePboardType] precomposedStringWithCanonicalMapping]; |
| DOMText *text = [document createTextNode:URLTitleString]; |
| [anchor setHref:URLString]; |
| [anchor appendChild:text]; |
| DOMDocumentFragment *fragment = [document createDocumentFragment]; |
| [fragment appendChild:anchor]; |
| return fragment; |
| } |
| |
| if ([pboardType isEqualToString:WebCore::legacyStringPasteboardType()]) { |
| if (!context) |
| return nil; |
| auto string = [[pasteboard stringForType:WebCore::legacyStringPasteboardType()] precomposedStringWithCanonicalMapping]; |
| return kit(createFragmentFromText(makeSimpleRange(*core(context)), string).ptr()); |
| } |
| |
| return nil; |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| - (BOOL)_isUsingAcceleratedCompositing |
| { |
| return _private->layerHostingView != nil; |
| } |
| |
| - (NSView *)_compositingLayersHostingView |
| { |
| return _private->layerHostingView; |
| } |
| |
| - (BOOL)_isInPrintMode |
| { |
| return _private->printing; |
| } |
| |
| - (BOOL)_beginPrintModeWithMinimumPageWidth:(CGFloat)minimumPageWidth height:(CGFloat)minimumPageHeight maximumPageWidth:(CGFloat)maximumPageWidth |
| { |
| auto* frame = core([self _frame]); |
| if (!frame) |
| return NO; |
| |
| if (frame->document() && frame->document()->isFrameSet()) { |
| minimumPageWidth = 0; |
| minimumPageHeight = 0; |
| } |
| |
| float maximumShrinkRatio = 0; |
| if (minimumPageWidth > 0.0) |
| maximumShrinkRatio = maximumPageWidth / minimumPageWidth; |
| |
| [self _setPrinting:YES minimumPageLogicalWidth:minimumPageWidth logicalHeight:minimumPageHeight originalPageWidth:minimumPageWidth originalPageHeight:minimumPageHeight maximumShrinkRatio:maximumShrinkRatio adjustViewSize:YES paginateScreenContent:[self _isInScreenPaginationMode]]; |
| return YES; |
| } |
| |
| - (BOOL)_beginPrintModeWithPageWidth:(float)pageWidth height:(float)pageHeight shrinkToFit:(BOOL)shrinkToFit |
| { |
| auto* frame = core([self _frame]); |
| if (!frame) |
| return NO; |
| |
| auto* document = frame->document(); |
| bool isHorizontal = !document || !document->renderView() || document->renderView()->style().isHorizontalWritingMode(); |
| |
| float pageLogicalWidth = isHorizontal ? pageWidth : pageHeight; |
| float pageLogicalHeight = isHorizontal ? pageHeight : pageWidth; |
| WebCore::FloatSize minLayoutSize(pageLogicalWidth, pageLogicalHeight); |
| float maximumShrinkRatio = 1; |
| |
| // If we are a frameset just print with the layout we have onscreen, otherwise relayout |
| // according to the page width. |
| if (shrinkToFit && (!frame->document() || !frame->document()->isFrameSet())) { |
| minLayoutSize = frame->resizePageRectsKeepingRatio(WebCore::FloatSize(pageLogicalWidth, pageLogicalHeight), WebCore::FloatSize(pageLogicalWidth * _WebHTMLViewPrintingMinimumShrinkFactor, pageLogicalHeight * _WebHTMLViewPrintingMinimumShrinkFactor)); |
| maximumShrinkRatio = _WebHTMLViewPrintingMaximumShrinkFactor / _WebHTMLViewPrintingMinimumShrinkFactor; |
| } |
| |
| [self _setPrinting:YES minimumPageLogicalWidth:minLayoutSize.width() logicalHeight:minLayoutSize.height() originalPageWidth:pageLogicalWidth originalPageHeight:pageLogicalHeight maximumShrinkRatio:maximumShrinkRatio adjustViewSize:YES paginateScreenContent:[self _isInScreenPaginationMode]]; |
| |
| return YES; |
| } |
| |
| - (void)_endPrintMode |
| { |
| [self _setPrinting:NO minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:YES paginateScreenContent:[self _isInScreenPaginationMode]]; |
| } |
| |
| - (BOOL)_isInScreenPaginationMode |
| { |
| return _private->paginateScreenContent; |
| } |
| |
| - (BOOL)_beginScreenPaginationModeWithPageSize:(CGSize)pageSize shrinkToFit:(BOOL)shrinkToFit |
| { |
| auto* frame = core([self _frame]); |
| if (!frame) |
| return NO; |
| |
| auto* document = frame->document(); |
| bool isHorizontal = !document || !document->renderView() || document->renderView()->style().isHorizontalWritingMode(); |
| |
| float pageLogicalWidth = isHorizontal ? pageSize.width : pageSize.height; |
| float pageLogicalHeight = isHorizontal ? pageSize.height : pageSize.width; |
| WebCore::FloatSize minLayoutSize(pageLogicalWidth, pageLogicalHeight); |
| float maximumShrinkRatio = 1; |
| |
| // If we are a frameset just print with the layout we have onscreen, otherwise relayout |
| // according to the page width. |
| if (shrinkToFit && (!frame->document() || !frame->document()->isFrameSet())) { |
| minLayoutSize = frame->resizePageRectsKeepingRatio(WebCore::FloatSize(pageLogicalWidth, pageLogicalHeight), WebCore::FloatSize(pageLogicalWidth * _WebHTMLViewPrintingMinimumShrinkFactor, pageLogicalHeight * _WebHTMLViewPrintingMinimumShrinkFactor)); |
| maximumShrinkRatio = _WebHTMLViewPrintingMaximumShrinkFactor / _WebHTMLViewPrintingMinimumShrinkFactor; |
| } |
| |
| [self _setPrinting:[self _isInPrintMode] minimumPageLogicalWidth:minLayoutSize.width() logicalHeight:minLayoutSize.height() originalPageWidth:pageLogicalWidth originalPageHeight:pageLogicalHeight maximumShrinkRatio:maximumShrinkRatio adjustViewSize:YES paginateScreenContent:[self _isInScreenPaginationMode]]; |
| |
| return YES; |
| } |
| |
| - (void)_endScreenPaginationMode |
| { |
| [self _setPrinting:[self _isInPrintMode] minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:YES paginateScreenContent:NO]; |
| } |
| |
| - (CGFloat)_adjustedBottomOfPageWithTop:(CGFloat)top bottom:(CGFloat)bottom limit:(CGFloat)bottomLimit |
| { |
| auto* frame = core([self _frame]); |
| if (!frame) |
| return bottom; |
| |
| auto* view = frame->view(); |
| if (!view) |
| return bottom; |
| |
| float newBottom; |
| view->adjustPageHeightDeprecated(&newBottom, top, bottom, bottomLimit); |
| |
| // If the new bottom is equal to the old bottom (when both are treated as floats), we just return the original |
| // bottom. This prevents rounding errors that can occur when converting newBottom to a double. |
| if (WTF::areEssentiallyEqual(static_cast<float>(bottom), newBottom)) |
| return bottom; |
| return newBottom; |
| } |
| |
| #if PLATFORM(IOS_FAMILY) |
| |
| - (id)accessibilityRootElement |
| { |
| return [[self _frame] accessibilityRoot]; |
| } |
| |
| #endif |
| |
| #if PLATFORM(MAC) |
| |
| - (NSView *)_hitViewForEvent:(NSEvent *)event |
| { |
| // Usually, we hack AK's hitTest method to catch all events at the topmost WebHTMLView. |
| // Callers of this method, however, want to query the deepest view instead. |
| forceNSViewHitTest = YES; |
| NSView *hitView = [(NSView *)[[self window] contentView] hitTest:[event locationInWindow]]; |
| forceNSViewHitTest = NO; |
| return hitView; |
| } |
| |
| #endif |
| |
| @end |
| |
| @implementation NSView (WebHTMLViewFileInternal) |
| |
| - (void)_web_addDescendentWebHTMLViewsToArray:(NSMutableArray *)array |
| { |
| for (NSView *child in [self subviews]) { |
| if ([child isKindOfClass:[WebHTMLView class]]) |
| [array addObject:child]; |
| [child _web_addDescendentWebHTMLViewsToArray:array]; |
| } |
| } |
| |
| @end |
| |
| @implementation WebHTMLView |
| |
| #if PLATFORM(MAC) |
| |
| + (void)initialize |
| { |
| [NSApp registerServicesMenuSendTypes:[[self class] _selectionPasteboardTypes] returnTypes:[[self class] _insertablePasteboardTypes]]; |
| |
| JSC::initialize(); |
| WTF::initializeMainThread(); |
| WebCore::populateJITOperations(); |
| } |
| |
| #endif |
| |
| - (id)initWithFrame:(NSRect)frame |
| { |
| self = [super initWithFrame:frame]; |
| if (!self) |
| return nil; |
| |
| #if PLATFORM(MAC) |
| [self setFocusRingType:NSFocusRingTypeNone]; |
| #endif |
| |
| // Make all drawing go through us instead of subviews. |
| [self _setDrawsOwnDescendants:YES]; |
| |
| _private = [[WebHTMLViewPrivate alloc] init]; |
| |
| _private->pluginController = adoptNS([[WebPluginController alloc] initWithDocumentView:self]); |
| |
| #if PLATFORM(IOS_FAMILY) |
| [[NSNotificationCenter defaultCenter] |
| addObserver:self selector:@selector(markedTextUpdate:) |
| name:WebMarkedTextUpdatedNotification object:nil]; |
| auto notificationName = adoptNS([[NSString alloc] initWithCString:kGSEventHardwareKeyboardAvailabilityChangedNotification encoding:NSUTF8StringEncoding]); |
| auto notificationBehavior = static_cast<CFNotificationSuspensionBehavior>(CFNotificationSuspensionBehaviorCoalesce | _CFNotificationObserverIsObjC); |
| CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)(self), hardwareKeyboardAvailabilityChangedCallback, (__bridge CFStringRef)notificationName.get(), nullptr, notificationBehavior); |
| #endif |
| |
| #if PLATFORM(MAC) |
| _private->softSpaceRange = NSMakeRange(NSNotFound, 0); |
| #endif |
| |
| return self; |
| } |
| |
| - (void)dealloc |
| { |
| if (WebCoreObjCScheduleDeallocateOnMainThread([WebHTMLView class], self)) |
| return; |
| |
| #if PLATFORM(IOS_FAMILY) |
| [[NSNotificationCenter defaultCenter] removeObserver:self name:WebMarkedTextUpdatedNotification object:nil]; |
| auto notificationName = adoptNS([[NSString alloc] initWithCString:kGSEventHardwareKeyboardAvailabilityChangedNotification encoding:NSUTF8StringEncoding]); |
| CFNotificationCenterRemoveObserver(CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)(self), (__bridge CFStringRef)notificationName.get(), nullptr); |
| #endif |
| |
| // We can't assert that close has already been called because |
| // this view can be removed from it's superview, even though |
| // it could be needed later, so close if needed. |
| [self close]; |
| [_private release]; |
| _private = nil; |
| |
| [super dealloc]; |
| } |
| |
| // Returns YES if the delegate returns YES (so we should do no more work). |
| - (BOOL)callDelegateDoCommandBySelectorIfNeeded:(SEL)selector |
| { |
| BOOL callerAlreadyCalledDelegate = _private->selectorForDoCommandBySelector == selector; |
| _private->selectorForDoCommandBySelector = 0; |
| if (callerAlreadyCalledDelegate) |
| return NO; |
| WebView *webView = [self _webView]; |
| return [[webView _editingDelegateForwarder] webView:webView doCommandBySelector:selector]; |
| } |
| |
| 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"_s); |
| map->add(@selector(insertParagraphSeparator:), "InsertNewline"_s); |
| map->add(@selector(insertTabIgnoringFieldEditor:), "InsertTab"_s); |
| map->add(@selector(pageDown:), "MovePageDown"_s); |
| map->add(@selector(pageDownAndModifySelection:), "MovePageDownAndModifySelection"_s); |
| map->add(@selector(pageUp:), "MovePageUp"_s); |
| map->add(@selector(pageUpAndModifySelection:), "MovePageUpAndModifySelection"_s); |
| |
| 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); |
| } |
| |
| - (WebCore::Editor::Command)coreCommandBySelector:(SEL)selector |
| { |
| auto* coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return WebCore::Editor::Command(); |
| return coreFrame->editor().command(commandNameForSelector(selector)); |
| } |
| |
| - (WebCore::Editor::Command)coreCommandByName:(const char*)name |
| { |
| auto* coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return WebCore::Editor::Command(); |
| return coreFrame->editor().command(String::fromLatin1(name)); |
| } |
| |
| - (void)executeCoreCommandBySelector:(SEL)selector |
| { |
| if ([self callDelegateDoCommandBySelectorIfNeeded:selector]) |
| return; |
| [self coreCommandBySelector:selector].execute(); |
| } |
| |
| - (void)executeCoreCommandByName:(const char*)name |
| { |
| [self coreCommandByName:name].execute(); |
| } |
| |
| // These commands are forwarded to the Editor object in WebCore. |
| // Ideally we'd do this for all editing commands; more of the code |
| // should be moved from here to there, and more commands should be |
| // added to this list. |
| |
| // FIXME: Maybe we should set things up so that all these share a single method implementation function. |
| // The functions are identical. |
| |
| #define WEBCORE_COMMAND(command) - (void)command:(id)sender { [self executeCoreCommandBySelector:_cmd]; } |
| |
| WEBCORE_COMMAND(alignCenter) |
| WEBCORE_COMMAND(alignJustified) |
| WEBCORE_COMMAND(alignLeft) |
| WEBCORE_COMMAND(alignRight) |
| WEBCORE_COMMAND(copy) |
| WEBCORE_COMMAND(cut) |
| WEBCORE_COMMAND(paste) |
| 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(overWrite) |
| WEBCORE_COMMAND(pageDown) |
| WEBCORE_COMMAND(pageDownAndModifySelection) |
| WEBCORE_COMMAND(pageUp) |
| WEBCORE_COMMAND(pageUpAndModifySelection) |
| WEBCORE_COMMAND(pasteAsPlainText) |
| 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(transpose) |
| WEBCORE_COMMAND(underline) |
| WEBCORE_COMMAND(unscript) |
| WEBCORE_COMMAND(yank) |
| WEBCORE_COMMAND(yankAndSelect) |
| |
| #if PLATFORM(IOS_FAMILY) |
| WEBCORE_COMMAND(clearText) |
| WEBCORE_COMMAND(toggleBold) |
| WEBCORE_COMMAND(toggleItalic) |
| WEBCORE_COMMAND(toggleUnderline) |
| #endif |
| |
| #undef WEBCORE_COMMAND |
| |
| #define COMMAND_PROLOGUE if ([self callDelegateDoCommandBySelectorIfNeeded:_cmd]) return; |
| |
| #if PLATFORM(MAC) |
| |
| - (IBAction)takeFindStringFromSelection:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| if (![self _hasSelection]) { |
| NSBeep(); |
| return; |
| } |
| |
| [NSPasteboard _web_setFindPasteboardString:[self selectedString] withOwner:self]; |
| } |
| |
| // This method is needed to support macOS services. |
| - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pasteboard types:(NSArray *)types |
| { |
| [pasteboard declareTypes:types owner:[self _topHTMLView]]; |
| [self writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard]; |
| return YES; |
| } |
| |
| - (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)returnType |
| { |
| BOOL isSendTypeOK = !sendType || ([[self pasteboardTypesForSelection] containsObject:sendType] && [self _hasSelection]); |
| BOOL isReturnTypeOK = NO; |
| if (!returnType) |
| isReturnTypeOK = YES; |
| else if ([[[self class] _insertablePasteboardTypes] containsObject:returnType] && [self _isEditable]) { |
| // We can insert strings in any editable context. We can insert other types, like images, only in rich edit contexts. |
| isReturnTypeOK = [returnType isEqualToString:WebCore::legacyStringPasteboardType()] || [self _canEditRichly]; |
| } |
| if (isSendTypeOK && isReturnTypeOK) |
| return self; |
| return [[self nextResponder] validRequestorForSendType:sendType returnType:returnType]; |
| } |
| |
| #endif |
| |
| // jumpToSelection is the old name for what AppKit now calls centerSelectionInVisibleArea. Safari |
| // was using the old jumpToSelection selector in its menu. Newer versions of Safari will use the |
| // selector centerSelectionInVisibleArea. We'll leave the old selector in place for two reasons: |
| // (1) Compatibility between older Safari and newer WebKit; (2) other WebKit-based applications |
| // might be using the selector, and we don't want to break them. |
| - (void)jumpToSelection:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| if (auto* coreFrame = core([self _frame])) |
| coreFrame->selection().revealSelection(WebCore::SelectionRevealMode::Reveal, WebCore::ScrollAlignment::alignCenterAlways); |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (BOOL)validateUserInterfaceItemWithoutDelegate:(id <NSValidatedUserInterfaceItem>)item |
| { |
| SEL action = [item action]; |
| RefPtr<WebCore::Frame> frame = core([self _frame]); |
| |
| if (!frame) |
| return NO; |
| |
| if (WebCore::Document* doc = frame->document()) { |
| if (doc->isPluginDocument()) |
| return NO; |
| if (doc->isImageDocument()) { |
| if (action == @selector(copy:)) |
| return frame->loader().isComplete(); |
| return NO; |
| } |
| } |
| if (action == @selector(changeSpelling:) |
| IGNORE_WARNINGS_BEGIN("undeclared-selector") |
| || action == @selector(_changeSpellingFromMenu:) |
| IGNORE_WARNINGS_END |
| || action == @selector(checkSpelling:) |
| || action == @selector(complete:) |
| || action == @selector(pasteFont:)) |
| return [self _canEdit]; |
| |
| if (action == @selector(showGuessPanel:)) { |
| NSMenuItem *menuItem = (NSMenuItem *)item; |
| if ([menuItem isKindOfClass:[NSMenuItem class]]) { |
| BOOL panelShowing = [[[NSSpellChecker sharedSpellChecker] spellingPanel] isVisible]; |
| [menuItem setTitle:panelShowing |
| ? UI_STRING_INTERNAL("Hide Spelling and Grammar", "menu item title") |
| : UI_STRING_INTERNAL("Show Spelling and Grammar", "menu item title")]; |
| } |
| return [self _canEdit]; |
| } |
| |
| if (action == @selector(changeBaseWritingDirection:) |
| || action == @selector(makeBaseWritingDirectionLeftToRight:) |
| || action == @selector(makeBaseWritingDirectionRightToLeft:)) { |
| NSWritingDirection writingDirection; |
| |
| if (action == @selector(changeBaseWritingDirection:)) { |
| writingDirection = static_cast<NSWritingDirection>([item tag]); |
| if (writingDirection == NSWritingDirectionNatural) |
| return NO; |
| } else if (action == @selector(makeBaseWritingDirectionLeftToRight:)) |
| writingDirection = NSWritingDirectionLeftToRight; |
| else |
| writingDirection = NSWritingDirectionRightToLeft; |
| |
| NSMenuItem *menuItem = (NSMenuItem *)item; |
| if ([menuItem isKindOfClass:[NSMenuItem class]]) { |
| String direction = writingDirection == NSWritingDirectionLeftToRight ? "ltr"_s : "rtl"_s; |
| [menuItem setState:(frame->editor().selectionHasStyle(WebCore::CSSPropertyDirection, direction) != TriState::False)]; |
| } |
| return [self _canEdit]; |
| } |
| |
| if (action == @selector(makeBaseWritingDirectionNatural:)) { |
| NSMenuItem *menuItem = (NSMenuItem *)item; |
| if ([menuItem isKindOfClass:[NSMenuItem class]]) |
| [menuItem setState:NSControlStateValueOff]; |
| return NO; |
| } |
| |
| if (action == @selector(toggleBaseWritingDirection:)) { |
| NSMenuItem *menuItem = (NSMenuItem *)item; |
| if ([menuItem isKindOfClass:[NSMenuItem class]]) { |
| // Take control of the title of the menu item instead of just checking/unchecking it because |
| // a check would be ambiguous. |
| [menuItem setTitle:(frame->editor().selectionHasStyle(WebCore::CSSPropertyDirection, "rtl"_s) != TriState::False) |
| ? UI_STRING_INTERNAL("Left to Right", "Left to Right context menu item") |
| : UI_STRING_INTERNAL("Right to Left", "Right to Left context menu item")]; |
| } |
| return [self _canEdit]; |
| } |
| |
| if (action == @selector(changeAttributes:) |
| || action == @selector(changeColor:) |
| || action == @selector(changeFont:)) |
| return [self _canEditRichly]; |
| |
| if (action == @selector(capitalizeWord:) |
| || action == @selector(lowercaseWord:) |
| || action == @selector(uppercaseWord:)) |
| return [self _hasSelection] && [self _isEditable]; |
| |
| if (action == @selector(centerSelectionInVisibleArea:) |
| || action == @selector(jumpToSelection:) |
| || action == @selector(copyFont:)) |
| return [self _hasSelection] || ([self _isEditable] && [self _hasInsertionPoint]); |
| |
| if (action == @selector(changeDocumentBackgroundColor:)) |
| return [[self _webView] isEditable] && [self _canEditRichly]; |
| |
| IGNORE_WARNINGS_BEGIN("undeclared-selector") |
| if (action == @selector(_ignoreSpellingFromMenu:) |
| || action == @selector(_learnSpellingFromMenu:) |
| IGNORE_WARNINGS_END |
| || action == @selector(takeFindStringFromSelection:)) |
| return [self _hasSelection]; |
| |
| if (action == @selector(paste:) || action == @selector(pasteAsPlainText:)) |
| return frame && (frame->editor().canDHTMLPaste() || frame->editor().canPaste()); |
| |
| if (action == @selector(pasteAsRichText:)) |
| return frame && (frame->editor().canDHTMLPaste() |
| || (frame->editor().canPaste() && frame->selection().selection().isContentRichlyEditable())); |
| |
| if (action == @selector(performFindPanelAction:)) |
| return NO; |
| |
| if (action == @selector(_lookUpInDictionaryFromMenu:)) |
| return [self _hasSelection]; |
| |
| if (action == @selector(stopSpeaking:)) |
| return [NSApp isSpeaking]; |
| |
| if (action == @selector(toggleGrammarChecking:)) { |
| // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must validate |
| // the selector here because we implement it here, and we must implement it here because the AppKit |
| // code checks the first responder. |
| NSMenuItem *menuItem = (NSMenuItem *)item; |
| if ([menuItem isKindOfClass:[NSMenuItem class]]) |
| [menuItem setState:[self isGrammarCheckingEnabled] ? NSControlStateValueOn : NSControlStateValueOff]; |
| return YES; |
| } |
| |
| if (action == @selector(orderFrontSubstitutionsPanel:)) { |
| NSMenuItem *menuItem = (NSMenuItem *)item; |
| if ([menuItem isKindOfClass:[NSMenuItem class]]) { |
| BOOL panelShowing = [[[NSSpellChecker sharedSpellChecker] substitutionsPanel] isVisible]; |
| [menuItem setTitle:panelShowing |
| ? UI_STRING_INTERNAL("Hide Substitutions", "menu item title") |
| : UI_STRING_INTERNAL("Show Substitutions", "menu item title")]; |
| } |
| return [self _canEdit]; |
| } |
| |
| // FIXME 4799134: WebView is the bottleneck for this logic, but we must validate |
| // the selector here because we implement it here, and we must implement it here because the AppKit |
| // code checks the first responder. |
| if (action == @selector(toggleSmartInsertDelete:)) { |
| NSMenuItem *menuItem = (NSMenuItem *)item; |
| if ([menuItem isKindOfClass:[NSMenuItem class]]) |
| [menuItem setState:[self smartInsertDeleteEnabled] ? NSControlStateValueOn : NSControlStateValueOff]; |
| return [self _canEdit]; |
| } |
| |
| if (action == @selector(toggleAutomaticQuoteSubstitution:)) { |
| NSMenuItem *menuItem = (NSMenuItem *)item; |
| if ([menuItem isKindOfClass:[NSMenuItem class]]) |
| [menuItem setState:[self isAutomaticQuoteSubstitutionEnabled] ? NSControlStateValueOn : NSControlStateValueOff]; |
| return [self _canEdit]; |
| } |
| |
| if (action == @selector(toggleAutomaticLinkDetection:)) { |
| NSMenuItem *menuItem = (NSMenuItem *)item; |
| if ([menuItem isKindOfClass:[NSMenuItem class]]) |
| [menuItem setState:[self isAutomaticLinkDetectionEnabled] ? NSControlStateValueOn : NSControlStateValueOff]; |
| return [self _canEdit]; |
| } |
| |
| if (action == @selector(toggleAutomaticDashSubstitution:)) { |
| NSMenuItem *menuItem = (NSMenuItem *)item; |
| if ([menuItem isKindOfClass:[NSMenuItem class]]) |
| [menuItem setState:[self isAutomaticDashSubstitutionEnabled] ? NSControlStateValueOn : NSControlStateValueOff]; |
| return [self _canEdit]; |
| } |
| |
| if (action == @selector(toggleAutomaticTextReplacement:)) { |
| NSMenuItem *menuItem = (NSMenuItem *)item; |
| if ([menuItem isKindOfClass:[NSMenuItem class]]) |
| [menuItem setState:[self isAutomaticTextReplacementEnabled] ? NSControlStateValueOn : NSControlStateValueOff]; |
| return [self _canEdit]; |
| } |
| |
| if (action == @selector(toggleAutomaticSpellingCorrection:)) { |
| NSMenuItem *menuItem = (NSMenuItem *)item; |
| if ([menuItem isKindOfClass:[NSMenuItem class]]) |
| [menuItem setState:[self isAutomaticSpellingCorrectionEnabled] ? NSControlStateValueOn : NSControlStateValueOff]; |
| return [self _canEdit]; |
| } |
| |
| auto command = [self coreCommandBySelector:action]; |
| if (command.isSupported()) { |
| NSMenuItem *menuItem = (NSMenuItem *)item; |
| if ([menuItem isKindOfClass:[NSMenuItem class]]) |
| [menuItem setState:kit(command.state())]; |
| return command.isEnabled(); |
| } |
| |
| return YES; |
| } |
| |
| - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item |
| { |
| // This can be called during teardown when _webView is nil. Return NO when this happens, because CallUIDelegateReturningBoolean |
| // assumes the WebVIew is non-nil. |
| if (![self _webView]) |
| return NO; |
| BOOL result = [self validateUserInterfaceItemWithoutDelegate:item]; |
| return CallUIDelegateReturningBoolean(result, [self _webView], @selector(webView:validateUserInterfaceItem:defaultValidation:), item, result); |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| - (BOOL)acceptsFirstResponder |
| { |
| // Don't accept first responder when we first click on this view. |
| // We have to pass the event down through WebCore first to be sure we don't hit a subview. |
| // Do accept first responder at any other time, for example from keyboard events, |
| // or from calls back from WebCore once we begin mouse-down event handling. |
| #if PLATFORM(MAC) |
| WebEvent *event = [NSApp currentEvent]; |
| #else |
| WebEvent *event = [WAKWindow currentEvent]; |
| #endif |
| if (event && event.type == WebEventMouseDown |
| && !_private->handlingMouseDownEvent |
| && NSPointInRect([event locationInWindow], [self convertRect:[self visibleRect] toView:nil])) |
| return NO; |
| return YES; |
| } |
| |
| - (BOOL)maintainsInactiveSelection |
| { |
| #if USE(UIKIT_EDITING) |
| // We want to maintain an inactive selection, when in editable content. |
| if ([[self _webView] maintainsInactiveSelection]) |
| return YES; |
| |
| if ([[self window] _newFirstResponderAfterResigning] == self) |
| return YES; |
| |
| auto* coreFrame = core([self _frame]); |
| return coreFrame && coreFrame->selection().selection().isContentEditable(); |
| #else |
| // This method helps to determine whether the WebHTMLView should maintain |
| // an inactive selection when it's not first responder. |
| // Traditionally, these views have not maintained such selections, |
| // clearing them when the view was not first responder. However, |
| // to fix bugs like this one: |
| // <rdar://problem/3672088>: "Editable WebViews should maintain a selection even |
| // when they're not firstResponder" |
| // it was decided to add a switch to act more like an NSTextView. |
| |
| if ([[self _webView] maintainsInactiveSelection]) |
| return YES; |
| |
| // Predict the case where we are losing first responder status only to |
| // gain it back again. Want to keep the selection in that case. |
| id nextResponder = [[self window] _newFirstResponderAfterResigning]; |
| if ([nextResponder isKindOfClass:[NSScrollView class]]) { |
| id contentView = [nextResponder contentView]; |
| if (contentView) |
| nextResponder = contentView; |
| } |
| if ([nextResponder isKindOfClass:[NSClipView class]]) { |
| id documentView = [nextResponder documentView]; |
| if (documentView) |
| nextResponder = documentView; |
| } |
| if (nextResponder == self) |
| return YES; |
| |
| auto* coreFrame = core([self _frame]); |
| bool selectionIsEditable = coreFrame && coreFrame->selection().selection().isContentEditable(); |
| bool nextResponderIsInWebView = [nextResponder isKindOfClass:[NSView class]] |
| && [nextResponder isDescendantOf:[[[self _webView] mainFrame] frameView]]; |
| |
| return selectionIsEditable && nextResponderIsInWebView; |
| #endif |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (void)addSuperviewObservers |
| { |
| if (_private->observingSuperviewNotifications) |
| return; |
| |
| NSView *superview = [self superview]; |
| if (!superview || ![self window]) |
| return; |
| |
| NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; |
| [notificationCenter addObserver:self selector:@selector(_frameOrBoundsChanged) name:NSViewFrameDidChangeNotification object:superview]; |
| [notificationCenter addObserver:self selector:@selector(_frameOrBoundsChanged) name:NSViewBoundsDidChangeNotification object:superview]; |
| |
| // In addition to registering for frame/bounds change notifications, call -_frameOrBoundsChanged. |
| // It will check the current scroll against the previous layout's scroll. We need to |
| // do this here to catch the case where the WebView is laid out at one size, removed from its |
| // window, resized, and inserted into another window. Our frame/bounds changed notifications |
| // will not be sent in that situation, since we only watch for changes while in the view hierarchy. |
| [self _frameOrBoundsChanged]; |
| |
| _private->observingSuperviewNotifications = true; |
| } |
| |
| - (void)addWindowObservers |
| { |
| if (_private->observingWindowNotifications) |
| return; |
| |
| NSWindow *window = [self window]; |
| if (!window) |
| return; |
| |
| NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; |
| [notificationCenter addObserver:self selector:@selector(windowDidBecomeKey:) name:NSWindowDidBecomeKeyNotification object:nil]; |
| [notificationCenter addObserver:self selector:@selector(windowDidResignKey:) name:NSWindowDidResignKeyNotification object:nil]; |
| [notificationCenter addObserver:self selector:@selector(windowWillClose:) name:NSWindowWillCloseNotification object:window]; |
| |
| _private->observingWindowNotifications = true; |
| } |
| |
| - (void)viewWillMoveToSuperview:(NSView *)newSuperview |
| { |
| [self _removeSuperviewObservers]; |
| } |
| |
| - (void)viewDidMoveToSuperview |
| { |
| if ([self superview] != nil) |
| [self addSuperviewObservers]; |
| |
| if ([self superview] && [self _isUsingAcceleratedCompositing]) { |
| WebView *webView = [self _webView]; |
| if ([webView _postsAcceleratedCompositingNotifications]) |
| [[NSNotificationCenter defaultCenter] postNotificationName:_WebViewDidStartAcceleratedCompositingNotification object:webView userInfo:nil]; |
| } |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| - (void)viewWillMoveToWindow:(NSWindow *)window |
| { |
| // Don't do anything if we aren't initialized. This happens |
| // when decoding a WebView. When WebViews are decoded their subviews |
| // are created by initWithCoder: and so won't be normally |
| // initialized. The stub views are discarded by WebView. |
| if (!_private) |
| return; |
| |
| #if PLATFORM(MAC) |
| // FIXME: Some of these calls may not work because this view may be already removed from it's superview. |
| [self _removeWindowObservers]; |
| [self _removeSuperviewObservers]; |
| #endif |
| |
| // FIXME: This accomplishes the same thing as the call to setCanStartMedia(false) in |
| // WebView. It would be nice to have a single mechanism instead of two. |
| [[self _pluginController] stopAllPlugins]; |
| } |
| |
| - (void)viewDidMoveToWindow |
| { |
| // Don't do anything if we aren't initialized. This happens |
| // when decoding a WebView. When WebViews are decoded their subviews |
| // are created by initWithCoder: and so won't be normally |
| // initialized. The stub views are discarded by WebView. |
| if (!_private || _private->closed) |
| return; |
| |
| [self _stopAutoscrollTimer]; |
| if ([self window]) { |
| _private->lastScrollPosition = [[self superview] bounds].origin; |
| #if PLATFORM(MAC) |
| [self addWindowObservers]; |
| [self addSuperviewObservers]; |
| #endif |
| |
| // FIXME: This accomplishes the same thing as the call to setCanStartMedia(true) in |
| // WebView. It would be nice to have a single mechanism instead of two. |
| [[self _pluginController] startAllPlugins]; |
| |
| _private->lastScrollPosition = NSZeroPoint; |
| |
| #if PLATFORM(MAC) |
| if (!_private->flagsChangedEventMonitor) { |
| __block WebHTMLView *weakSelf = self; |
| _private->flagsChangedEventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskFlagsChanged handler:^(NSEvent *flagsChangedEvent) { |
| [weakSelf _postFakeMouseMovedEventForFlagsChangedEvent:flagsChangedEvent]; |
| return flagsChangedEvent; |
| }]; |
| } |
| } else { |
| [NSEvent removeMonitor:_private->flagsChangedEventMonitor]; |
| _private->flagsChangedEventMonitor = nil; |
| #endif |
| } |
| } |
| |
| - (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow |
| { |
| } |
| |
| - (void)viewDidMoveToHostWindow |
| { |
| } |
| |
| - (void)addSubview:(NSView *)view |
| { |
| [super addSubview:view]; |
| |
| if ([WebPluginController isPlugInView:view]) { |
| |
| #if PLATFORM(IOS_FAMILY) |
| WebView *webView = [self _webView]; |
| [[webView _UIKitDelegateForwarder] webView:webView willAddPlugInView:view]; |
| #endif |
| |
| [[self _pluginController] addPlugin:view]; |
| } |
| } |
| |
| - (void)willRemoveSubview:(NSView *)subview |
| { |
| if ([WebPluginController isPlugInView:subview]) |
| [[self _pluginController] destroyPlugin:subview]; |
| |
| [super willRemoveSubview:subview]; |
| } |
| |
| - (void)reapplyStyles |
| { |
| #ifdef LOG_TIMES |
| double start = CFAbsoluteTimeGetCurrent(); |
| #endif |
| |
| if (auto* coreFrame = core([self _frame])) { |
| coreFrame->document()->styleScope().didChangeStyleSheetEnvironment(); |
| coreFrame->document()->updateStyleIfNeeded(); |
| } |
| |
| #ifdef LOG_TIMES |
| double thisTime = CFAbsoluteTimeGetCurrent() - start; |
| LOG(Timing, "%s apply style seconds = %f", [self URL], thisTime); |
| #endif |
| } |
| |
| // Do a layout, but set up a new fixed width for the purposes of doing printing layout. |
| // minPageWidth==0 implies a non-printing layout |
| - (void)layoutToMinimumPageWidth:(float)minPageLogicalWidth height:(float)minPageLogicalHeight originalPageWidth:(float)originalPageWidth originalPageHeight:(float)originalPageHeight maximumShrinkRatio:(float)maximumShrinkRatio adjustingViewSize:(BOOL)adjustViewSize |
| { |
| auto* coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return; |
| if (coreFrame->document()) { |
| if (coreFrame->document()->backForwardCacheState() != WebCore::Document::NotInBackForwardCache) |
| return; |
| coreFrame->document()->updateStyleIfNeeded(); |
| } |
| |
| if (![self _needsLayout]) |
| return; |
| |
| #ifdef LOG_TIMES |
| double start = CFAbsoluteTimeGetCurrent(); |
| #endif |
| |
| LOG(View, "%@ doing layout", self); |
| |
| if (auto* coreView = coreFrame->view()) { |
| if (minPageLogicalWidth > 0.0) { |
| WebCore::FloatSize pageSize(minPageLogicalWidth, minPageLogicalHeight); |
| WebCore::FloatSize originalPageSize(originalPageWidth, originalPageHeight); |
| if (coreFrame->document() && coreFrame->document()->renderView() && !coreFrame->document()->renderView()->style().isHorizontalWritingMode()) { |
| pageSize = WebCore::FloatSize(minPageLogicalHeight, minPageLogicalWidth); |
| originalPageSize = WebCore::FloatSize(originalPageHeight, originalPageWidth); |
| } |
| coreView->forceLayoutForPagination(pageSize, originalPageSize, maximumShrinkRatio, adjustViewSize ? WebCore::AdjustViewSize : WebCore::DoNotAdjustViewSize); |
| } else { |
| coreView->forceLayout(!adjustViewSize); |
| if (adjustViewSize) |
| coreView->adjustViewSize(); |
| } |
| } |
| |
| #ifdef LOG_TIMES |
| double thisTime = CFAbsoluteTimeGetCurrent() - start; |
| LOG(Timing, "%s layout seconds = %f", [self URL], thisTime); |
| #endif |
| } |
| |
| - (void)layout |
| { |
| [self layoutToMinimumPageWidth:0 height:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustingViewSize:NO]; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| // Deliver mouseup events to the DOM for button 2. |
| - (void)rightMouseUp:(NSEvent *)event |
| { |
| // There's a chance that if we run a nested event loop the event will be released. |
| // Retaining and then autoreleasing prevents that from causing a problem later here or |
| // inside AppKit code. |
| retainPtr(event).autorelease(); |
| |
| [super rightMouseUp:event]; |
| |
| if (auto* coreFrame = core([self _frame])) |
| coreFrame->eventHandler().mouseUp(event, [[self _webView] _pressureEvent]); |
| } |
| |
| static BOOL isPreVersion3Client(void) |
| { |
| static BOOL preVersion3Client = !WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_3_0_CONTEXT_MENU_TAGS); |
| return preVersion3Client; |
| } |
| |
| static BOOL isPreInspectElementTagClient(void) |
| { |
| static BOOL preInspectElementTagClient = !WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITH_INSPECT_ELEMENT_MENU_TAG); |
| return preInspectElementTagClient; |
| } |
| |
| enum { |
| // The next three values were used in WebKit 2.0 for SPI. In WebKit 3.0 these are API, with different values. |
| OldWebMenuItemTagSearchInSpotlight = 1000, |
| OldWebMenuItemTagSearchWeb, |
| OldWebMenuItemTagLookUpInDictionary, |
| }; |
| |
| static RetainPtr<NSArray> fixMenusToSendToOldClients(NSMutableArray *defaultMenuItems) |
| { |
| auto savedItems = adoptNS([[NSMutableArray alloc] init]); |
| |
| unsigned defaultItemsCount = [defaultMenuItems count]; |
| |
| if (isPreInspectElementTagClient() && defaultItemsCount >= 2) { |
| NSMenuItem *secondToLastItem = [defaultMenuItems objectAtIndex:defaultItemsCount - 2]; |
| NSMenuItem *lastItem = [defaultMenuItems objectAtIndex:defaultItemsCount - 1]; |
| |
| if ([secondToLastItem isSeparatorItem] && [lastItem tag] == WebMenuItemTagInspectElement) { |
| savedItems = adoptNS([[NSMutableArray alloc] initWithCapacity:2]); |
| [savedItems addObject:secondToLastItem]; |
| [savedItems addObject:lastItem]; |
| |
| [defaultMenuItems removeObject:secondToLastItem]; |
| [defaultMenuItems removeObject:lastItem]; |
| defaultItemsCount -= 2; |
| } |
| } |
| |
| BOOL preVersion3Client = isPreVersion3Client(); |
| if (!preVersion3Client) |
| return savedItems; |
| |
| for (NSMenuItem *item in defaultMenuItems) { |
| int tag = item.tag; |
| int oldStyleTag = tag; |
| |
| if (tag >= WEBMENUITEMTAG_WEBKIT_3_0_SPI_START) { |
| // Change all editing-related SPI tags listed in WebUIDelegatePrivate.h to WebMenuItemTagOther |
| // to match our old WebKit context menu behavior. |
| oldStyleTag = WebMenuItemTagOther; |
| } else { |
| // All items are expected to have useful tags coming into this method. |
| ASSERT(tag != WebMenuItemTagOther); |
| |
| // Use the pre-3.0 tags for the few items that changed tags as they moved from SPI to API. We |
| // do this only for old clients; new Mail already expects the new symbols in this case. |
| if (preVersion3Client) { |
| switch (tag) { |
| case WebMenuItemTagSearchInSpotlight: |
| oldStyleTag = OldWebMenuItemTagSearchInSpotlight; |
| break; |
| case WebMenuItemTagSearchWeb: |
| oldStyleTag = OldWebMenuItemTagSearchWeb; |
| break; |
| case WebMenuItemTagLookUpInDictionary: |
| oldStyleTag = OldWebMenuItemTagLookUpInDictionary; |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| item.tag = oldStyleTag; |
| } |
| |
| return savedItems; |
| } |
| |
| static RetainPtr<NSArray> fixMenusReceivedFromOldClients(NSArray *delegateSuppliedItems, NSArray *savedItems) |
| { |
| using namespace WebCore; |
| auto newMenuItems = adoptNS([delegateSuppliedItems mutableCopy]); |
| |
| if (savedItems) |
| [newMenuItems addObjectsFromArray:savedItems]; |
| |
| BOOL preVersion3Client = isPreVersion3Client(); |
| if (!preVersion3Client) |
| return newMenuItems; |
| |
| // Restore the modern tags to the menu items whose tags we altered in fixMenusToSendToOldClients. |
| for (NSMenuItem *item in newMenuItems.get()) { |
| int tag = [item tag]; |
| int modernTag = tag; |
| |
| if (tag == WebMenuItemTagOther) { |
| // Restore the specific tag for items on which we temporarily set WebMenuItemTagOther to match old behavior. |
| NSString *title = [item title]; |
| if ([title isEqualToString:contextMenuItemTagOpenLink()]) |
| modernTag = WebMenuItemTagOpenLink; |
| else if ([title isEqualToString:contextMenuItemTagIgnoreGrammar()]) |
| modernTag = WebMenuItemTagIgnoreGrammar; |
| else if ([title isEqualToString:contextMenuItemTagSpellingMenu()]) |
| modernTag = WebMenuItemTagSpellingMenu; |
| else if ([title isEqualToString:contextMenuItemTagShowSpellingPanel(true)] || [title isEqualToString:contextMenuItemTagShowSpellingPanel(false)]) |
| modernTag = WebMenuItemTagShowSpellingPanel; |
| else if ([title isEqualToString:contextMenuItemTagCheckSpelling()]) |
| modernTag = WebMenuItemTagCheckSpelling; |
| else if ([title isEqualToString:contextMenuItemTagCheckSpellingWhileTyping()]) |
| modernTag = WebMenuItemTagCheckSpellingWhileTyping; |
| else if ([title isEqualToString:contextMenuItemTagCheckGrammarWithSpelling()]) |
| modernTag = WebMenuItemTagCheckGrammarWithSpelling; |
| else if ([title isEqualToString:contextMenuItemTagFontMenu()]) |
| modernTag = WebMenuItemTagFontMenu; |
| else if ([title isEqualToString:contextMenuItemTagShowFonts()]) |
| modernTag = WebMenuItemTagShowFonts; |
| else if ([title isEqualToString:contextMenuItemTagBold()]) |
| modernTag = WebMenuItemTagBold; |
| else if ([title isEqualToString:contextMenuItemTagItalic()]) |
| modernTag = WebMenuItemTagItalic; |
| else if ([title isEqualToString:contextMenuItemTagUnderline()]) |
| modernTag = WebMenuItemTagUnderline; |
| else if ([title isEqualToString:contextMenuItemTagOutline()]) |
| modernTag = WebMenuItemTagOutline; |
| else if ([title isEqualToString:contextMenuItemTagStyles()]) |
| modernTag = WebMenuItemTagStyles; |
| else if ([title isEqualToString:contextMenuItemTagShowColors()]) |
| modernTag = WebMenuItemTagShowColors; |
| else if ([title isEqualToString:contextMenuItemTagSpeechMenu()]) |
| modernTag = WebMenuItemTagSpeechMenu; |
| else if ([title isEqualToString:contextMenuItemTagStartSpeaking()]) |
| modernTag = WebMenuItemTagStartSpeaking; |
| else if ([title isEqualToString:contextMenuItemTagStopSpeaking()]) |
| modernTag = WebMenuItemTagStopSpeaking; |
| else if ([title isEqualToString:contextMenuItemTagWritingDirectionMenu()]) |
| modernTag = WebMenuItemTagWritingDirectionMenu; |
| else if ([title isEqualToString:contextMenuItemTagDefaultDirection()]) |
| modernTag = WebMenuItemTagDefaultDirection; |
| else if ([title isEqualToString:contextMenuItemTagLeftToRight()]) |
| modernTag = WebMenuItemTagLeftToRight; |
| else if ([title isEqualToString:contextMenuItemTagRightToLeft()]) |
| modernTag = WebMenuItemTagRightToLeft; |
| else if ([title isEqualToString:contextMenuItemTagInspectElement()]) |
| modernTag = WebMenuItemTagInspectElement; |
| else if ([title isEqualToString:contextMenuItemTagCorrectSpellingAutomatically()]) |
| modernTag = WebMenuItemTagCorrectSpellingAutomatically; |
| else if ([title isEqualToString:contextMenuItemTagSubstitutionsMenu()]) |
| modernTag = WebMenuItemTagSubstitutionsMenu; |
| else if ([title isEqualToString:contextMenuItemTagShowSubstitutions(true)] || [title isEqualToString:contextMenuItemTagShowSubstitutions(false)]) |
| modernTag = WebMenuItemTagShowSubstitutions; |
| else if ([title isEqualToString:contextMenuItemTagSmartCopyPaste()]) |
| modernTag = WebMenuItemTagSmartCopyPaste; |
| else if ([title isEqualToString:contextMenuItemTagSmartQuotes()]) |
| modernTag = WebMenuItemTagSmartQuotes; |
| else if ([title isEqualToString:contextMenuItemTagSmartDashes()]) |
| modernTag = WebMenuItemTagSmartDashes; |
| else if ([title isEqualToString:contextMenuItemTagSmartLinks()]) |
| modernTag = WebMenuItemTagSmartLinks; |
| else if ([title isEqualToString:contextMenuItemTagTextReplacement()]) |
| modernTag = WebMenuItemTagTextReplacement; |
| else if ([title isEqualToString:contextMenuItemTagTransformationsMenu()]) |
| modernTag = WebMenuItemTagTransformationsMenu; |
| else if ([title isEqualToString:contextMenuItemTagMakeUpperCase()]) |
| modernTag = WebMenuItemTagMakeUpperCase; |
| else if ([title isEqualToString:contextMenuItemTagMakeLowerCase()]) |
| modernTag = WebMenuItemTagMakeLowerCase; |
| else if ([title isEqualToString:contextMenuItemTagCapitalize()]) |
| modernTag = WebMenuItemTagCapitalize; |
| else { |
| // We don't expect WebMenuItemTagOther for any items other than the ones we explicitly handle. |
| // There's nothing to prevent an app from applying this tag, but they are supposed to only |
| // use tags in the range starting with WebMenuItemBaseApplicationTag=10000 |
| ASSERT_NOT_REACHED(); |
| } |
| } else if (preVersion3Client) { |
| // Restore the new API tag for items on which we temporarily set the old SPI tag. The old SPI tag was |
| // needed to avoid confusing clients linked against earlier WebKits; the new API tag is needed for |
| // WebCore to handle the menu items appropriately (without needing to know about the old SPI tags). |
| switch (tag) { |
| case OldWebMenuItemTagSearchInSpotlight: |
| modernTag = WebMenuItemTagSearchInSpotlight; |
| break; |
| case OldWebMenuItemTagSearchWeb: |
| modernTag = WebMenuItemTagSearchWeb; |
| break; |
| case OldWebMenuItemTagLookUpInDictionary: |
| modernTag = WebMenuItemTagLookUpInDictionary; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| if (modernTag != tag) |
| [item setTag:modernTag]; |
| } |
| |
| return newMenuItems; |
| } |
| |
| static RetainPtr<NSMenuItem> createShareMenuItem(const WebCore::HitTestResult& hitTestResult) |
| { |
| auto items = adoptNS([[NSMutableArray alloc] init]); |
| |
| if (!hitTestResult.absoluteLinkURL().isEmpty()) { |
| NSURL *absoluteLinkURL = hitTestResult.absoluteLinkURL(); |
| [items addObject:absoluteLinkURL]; |
| } |
| |
| if (!hitTestResult.absoluteMediaURL().isEmpty() && hitTestResult.isDownloadableMedia()) { |
| NSURL *downloadableMediaURL = hitTestResult.absoluteMediaURL(); |
| [items addObject:downloadableMediaURL]; |
| } |
| |
| if (auto* image = hitTestResult.image()) { |
| if (RefPtr<const WebCore::FragmentedSharedBuffer> buffer = image->data()) |
| [items addObject:adoptNS([[NSImage alloc] initWithData:buffer->makeContiguous()->createNSData().get()]).get()]; |
| } |
| |
| if (!hitTestResult.selectedText().isEmpty()) { |
| NSString *selectedText = hitTestResult.selectedText(); |
| [items addObject:selectedText]; |
| } |
| |
| if (![items count]) |
| return nil; |
| |
| return [NSMenuItem standardShareMenuItemForItems:items.get()]; |
| } |
| |
| static RetainPtr<NSMutableArray> createMenuItems(const WebCore::HitTestResult& hitTestResult, const Vector<WebCore::ContextMenuItem>& items) |
| { |
| return createNSArray(items, [&] (auto& item) { |
| return createMenuItem(hitTestResult, item); |
| }); |
| } |
| |
| static RetainPtr<NSMenuItem> createMenuItem(const WebCore::HitTestResult& hitTestResult, const WebCore::ContextMenuItem& item) |
| { |
| #if HAVE(TRANSLATION_UI_SERVICES) |
| if (item.action() == WebCore::ContextMenuItemTagTranslate && !WebView._canHandleContextMenuTranslation) |
| return nil; |
| #endif |
| |
| if (item.action() == WebCore::ContextMenuItemTagShareMenu) |
| return createShareMenuItem(hitTestResult); |
| |
| switch (item.type()) { |
| case WebCore::ActionType: |
| case WebCore::CheckableActionType: { |
| auto menuItem = adoptNS([[NSMenuItem alloc] initWithTitle:item.title() action:@selector(forwardContextMenuAction:) keyEquivalent:@""]); |
| |
| if (auto tag = toTag(item.action())) |
| [menuItem setTag:*tag]; |
| [menuItem setEnabled:item.enabled()]; |
| [menuItem setState:item.checked() ? NSControlStateValueOn : NSControlStateValueOff]; |
| [menuItem setTarget:[WebMenuTarget sharedMenuTarget]]; |
| |
| return menuItem; |
| } |
| |
| case WebCore::SeparatorType: |
| return [NSMenuItem separatorItem]; |
| |
| case WebCore::SubmenuType: { |
| auto menu = adoptNS([[NSMenu alloc] init]); |
| { |
| auto submenuItems = createMenuItems(hitTestResult, item.subMenuItems()); |
| for (NSMenuItem *menuItem in submenuItems.get()) |
| [menu addItem:menuItem]; |
| } |
| |
| auto menuItem = adoptNS([[NSMenuItem alloc] initWithTitle:item.title() action:nullptr keyEquivalent:@""]); |
| |
| if (auto tag = toTag(item.action())) |
| [menuItem setTag:*tag]; |
| [menuItem setEnabled:item.enabled()]; |
| [menuItem setSubmenu:menu.get()]; |
| |
| return menuItem; |
| } |
| } |
| } |
| |
| static RetainPtr<NSArray> customMenuFromDefaultItems(WebView *webView, const WebCore::ContextMenu& defaultMenu) |
| { |
| const auto& hitTestResult = webView.page->contextMenuController().hitTestResult(); |
| bool isPopover = webView.window._childWindowOrderingPriority == NSWindowChildOrderingPriorityPopover; |
| bool isLookupDisabled = [NSUserDefaults.standardUserDefaults boolForKey:@"LULookupDisabled"]; |
| auto filteredItems = defaultMenu.items(); |
| |
| if (isLookupDisabled || isPopover) { |
| filteredItems.removeAllMatching([] (auto& item) { |
| return item.action() == WebCore::ContextMenuItemTagLookUpInDictionary; |
| }); |
| } |
| auto defaultMenuItems = createMenuItems(hitTestResult, filteredItems); |
| |
| id delegate = [webView UIDelegate]; |
| SEL selector = @selector(webView:contextMenuItemsForElement:defaultMenuItems:); |
| if (![delegate respondsToSelector:selector]) |
| return defaultMenuItems; |
| |
| auto element = adoptNS([[WebElementDictionary alloc] initWithHitTestResult:hitTestResult]); |
| |
| BOOL preVersion3Client = isPreVersion3Client(); |
| if (preVersion3Client) { |
| DOMNode *node = [element objectForKey:WebElementDOMNodeKey]; |
| if ([node isKindOfClass:[DOMHTMLInputElement class]] && [(DOMHTMLInputElement *)node _isTextField]) |
| return defaultMenuItems; |
| if ([node isKindOfClass:[DOMHTMLTextAreaElement class]]) |
| return defaultMenuItems; |
| } |
| |
| for (NSMenuItem *menuItem in defaultMenuItems.get()) { |
| if (!menuItem.representedObject) |
| menuItem.representedObject = element.get(); |
| } |
| |
| auto savedItems = fixMenusToSendToOldClients(defaultMenuItems.get()); |
| |
| NSArray *delegateSuppliedItems = CallUIDelegate(webView, selector, element.get(), defaultMenuItems.get()); |
| |
| return fixMenusReceivedFromOldClients(delegateSuppliedItems, savedItems.get()); |
| } |
| |
| - (NSMenu *)menuForEvent:(NSEvent *)event |
| { |
| // There's a chance that if we run a nested event loop the event will be released. |
| // Retaining and then autoreleasing prevents that from causing a problem later here or |
| // inside AppKit code. |
| retainPtr(event).autorelease(); |
| |
| [_private->completionController endRevertingChange:NO moveLeft:NO]; |
| |
| RefPtr<WebCore::Frame> coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return nil; |
| |
| auto* page = coreFrame->page(); |
| if (!page) |
| return nil; |
| |
| // Match behavior of other browsers by sending a mousedown event for right clicks. |
| _private->handlingMouseDownEvent = YES; |
| page->contextMenuController().clearContextMenu(); |
| coreFrame->eventHandler().mouseDown(event, [[self _webView] _pressureEvent]); |
| BOOL handledEvent = coreFrame->eventHandler().sendContextMenuEvent(WebCore::PlatformEventFactory::createPlatformMouseEvent(event, [[self _webView] _pressureEvent], page->chrome().platformPageClient())); |
| _private->handlingMouseDownEvent = NO; |
| |
| if (!handledEvent) |
| return nil; |
| |
| // Re-get page, since it might have gone away during event handling. |
| page = coreFrame->page(); |
| if (!page) |
| return nil; |
| |
| auto* contextMenu = page->contextMenuController().contextMenu(); |
| if (!contextMenu) |
| return nil; |
| |
| auto menuItems = customMenuFromDefaultItems([self _webView], *contextMenu); |
| if (![menuItems count]) |
| return nil; |
| |
| auto menu = adoptNS([[NSMenu alloc] init]); |
| |
| for (NSMenuItem *item in menuItems.get()) { |
| [menu addItem:item]; |
| |
| if (item.tag == WebCore::ContextMenuItemTagShareMenu) { |
| ASSERT([item.representedObject isKindOfClass:[NSSharingServicePicker class]]); |
| #if ENABLE(SERVICE_CONTROLS) |
| _private->currentSharingServicePickerController = adoptNS([[WebSharingServicePickerController alloc] initWithSharingServicePicker:item.representedObject client:static_cast<WebContextMenuClient&>(page->contextMenuController().client())]); |
| #endif |
| } |
| } |
| |
| [[WebMenuTarget sharedMenuTarget] setMenuController:&page->contextMenuController()]; |
| |
| return menu.autorelease(); |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| - (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag |
| { |
| return [self searchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag startInSelection:NO]; |
| } |
| |
| - (void)clearFocus |
| { |
| auto* coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return; |
| auto* document = coreFrame->document(); |
| if (!document) |
| return; |
| |
| document->setFocusedElement(0); |
| } |
| |
| - (BOOL)isOpaque |
| { |
| return [[self _webView] drawsBackground]; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (void)setLayer:(CALayer *)layer |
| { |
| if (auto* frame = core([self _frame])) { |
| if (auto* view = frame->view()) |
| view->setPaintsEntireContents(layer); |
| } |
| |
| [super setLayer:layer]; |
| } |
| |
| #endif |
| |
| #if !LOG_DISABLED |
| - (void)setNeedsDisplay:(BOOL)flag |
| { |
| LOG(View, "%@ setNeedsDisplay:%@", self, flag ? @"YES" : @"NO"); |
| [super setNeedsDisplay:flag]; |
| } |
| #endif |
| |
| static BOOL currentScrollIsBlit(NSView *clipView) |
| { |
| #if PLATFORM(MAC) |
| return [clipView isKindOfClass:[WebClipView class]] && [(WebClipView *)clipView currentScrollIsBlit]; |
| #else |
| return NO; |
| #endif |
| } |
| |
| // FIXME: this entire function could be #ifdeffed out on iOS. The below workaround is AppKit-specific. |
| - (void)setNeedsDisplayInRect:(NSRect)invalidRect |
| { |
| if (_private->inScrollPositionChanged && currentScrollIsBlit([self superview])) { |
| // When scrolling, the dirty regions are adjusted for the scroll only |
| // after NSViewBoundsDidChangeNotification is sent. Translate the invalid |
| // rect to pre-scrolled coordinates in order to get the right dirty region |
| // after adjustment. See <rdar://problem/7678927>. |
| NSPoint origin = [[self superview] bounds].origin; |
| invalidRect.origin.x -= _private->lastScrollPosition.x - origin.x; |
| invalidRect.origin.y -= _private->lastScrollPosition.y - origin.y; |
| } |
| [super setNeedsDisplayInRect:invalidRect]; |
| } |
| |
| - (void)setNeedsLayout: (BOOL)flag |
| { |
| LOG(View, "%@ setNeedsLayout:%@", self, flag ? @"YES" : @"NO"); |
| if (!flag) |
| return; // There's no way to say you don't need a layout. |
| if (auto* frame = core([self _frame])) { |
| if (frame->document() && frame->document()->backForwardCacheState() != WebCore::Document::NotInBackForwardCache) |
| return; |
| if (auto* view = frame->view()) |
| view->setNeedsLayoutAfterViewConfigurationChange(); |
| } |
| } |
| |
| - (void)setNeedsToApplyStyles: (BOOL)flag |
| { |
| LOG(View, "%@ setNeedsToApplyStyles:%@", self, flag ? @"YES" : @"NO"); |
| if (!flag) |
| return; // There's no way to say you don't need a style recalc. |
| if (auto* frame = core([self _frame])) { |
| if (frame->document() && frame->document()->backForwardCacheState() != WebCore::Document::NotInBackForwardCache) |
| return; |
| frame->document()->scheduleFullStyleRebuild(); |
| } |
| } |
| |
| - (void)drawSingleRect:(NSRect)rect |
| { |
| #if PLATFORM(MAC) |
| [NSGraphicsContext saveGraphicsState]; |
| NSRectClip(rect); |
| |
| ASSERT([[self superview] isKindOfClass:[WebClipView class]]); |
| |
| [(WebClipView *)[self superview] setAdditionalClip:rect]; |
| |
| @try { |
| if ([self _transparentBackground]) { |
| [[NSColor clearColor] set]; |
| NSRectFill (rect); |
| } |
| #endif |
| |
| [[self _frame] _drawRect:rect contentsOnly:YES]; |
| |
| #if PLATFORM(MAC) |
| WebView *webView = [self _webView]; |
| |
| // This hack is needed for <rdar://problem/5023545>. We can hit a race condition where drawRect will be |
| // called after the WebView has closed. If the client did not properly close the WebView and set the |
| // UIDelegate to nil, then the UIDelegate will be stale and this code will crash. |
| static BOOL version3OrLaterClient = WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITHOUT_QUICKBOOKS_QUIRK); |
| if (version3OrLaterClient) |
| [[webView _UIDelegateForwarder] webView:webView didDrawRect:[webView convertRect:rect fromView:self]]; |
| // Clients don't need support for the didDrawRect delegate method above on iOS. |
| // Also, a long time ago, when it was supported, it was part of a threading problem. |
| |
| if (WebNodeHighlight *currentHighlight = [webView currentNodeHighlight]) |
| [currentHighlight setNeedsUpdateInTargetViewRect:[self convertRect:rect toView:[currentHighlight targetView]]]; |
| |
| [(WebClipView *)[self superview] resetAdditionalClip]; |
| [NSGraphicsContext restoreGraphicsState]; |
| } @catch (NSException *localException) { |
| [(WebClipView *)[self superview] resetAdditionalClip]; |
| [NSGraphicsContext restoreGraphicsState]; |
| |
| LOG_ERROR("Exception caught while drawing: %@", localException); |
| [localException raise]; |
| } |
| #endif |
| } |
| |
| - (void)drawRect:(NSRect)rect |
| { |
| LOG(View, "%@ drawing", self); |
| |
| TraceScope scope(WebHTMLViewPaintStart, WebHTMLViewPaintEnd); |
| |
| #if PLATFORM(MAC) |
| const NSRect *rects; |
| NSInteger count; |
| [self getRectsBeingDrawn:&rects count:&count]; |
| |
| BOOL subviewsWereSetAside = _private->subviewsSetAside; |
| if (subviewsWereSetAside) |
| [self _restoreSubviews]; |
| #endif |
| |
| #ifdef LOG_TIMES |
| double start = CFAbsoluteTimeGetCurrent(); |
| #endif |
| |
| #if PLATFORM(MAC) |
| // If count == 0 here, use the rect passed in for drawing. This is a workaround for: |
| // <rdar://problem/3908282> REGRESSION (Mail): No drag image dragging selected text in Blot and Mail |
| // The reason for the workaround is that this method is called explicitly from the code |
| // to generate a drag image, and at that time, getRectsBeingDrawn:count: will return a zero count. |
| const int cRectThreshold = 10; |
| const float cWastedSpaceThreshold = 0.75f; |
| BOOL useUnionedRect = (count <= 1) || (count > cRectThreshold); |
| if (!useUnionedRect) { |
| // Attempt to guess whether or not we should use the unioned rect or the individual rects. |
| // We do this by computing the percentage of "wasted space" in the union. If that wasted space |
| // is too large, then we will do individual rect painting instead. |
| float unionPixels = (rect.size.width * rect.size.height); |
| float singlePixels = 0; |
| for (int i = 0; i < count; ++i) |
| singlePixels += rects[i].size.width * rects[i].size.height; |
| float wastedSpace = 1 - (singlePixels / unionPixels); |
| if (wastedSpace <= cWastedSpaceThreshold) |
| useUnionedRect = YES; |
| } |
| |
| if (useUnionedRect) |
| [self drawSingleRect:rect]; |
| else { |
| for (int i = 0; i < count; ++i) |
| [self drawSingleRect:rects[i]]; |
| } |
| #else |
| [self drawSingleRect:rect]; |
| #endif |
| |
| #ifdef LOG_TIMES |
| double thisTime = CFAbsoluteTimeGetCurrent() - start; |
| LOG(Timing, "%s draw seconds = %f", widget->part()->baseURL().URL().latin1(), thisTime); |
| #endif |
| |
| #if PLATFORM(MAC) |
| if (subviewsWereSetAside) |
| [self _setAsideSubviews]; |
| #endif |
| |
| WebView *webView = [self _webView]; |
| |
| #if PLATFORM(MAC) |
| // Only do the synchronization dance if we're drawing into the window, otherwise |
| // we risk disabling screen updates when no flush is pending. |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| if ([NSGraphicsContext currentContext] == [[self window] graphicsContext] && [webView _needsOneShotDrawingSynchronization]) { |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| // Disable screen updates to minimize the chances of the race between the CA |
| // display link and AppKit drawing causing flashes. |
| [[self window] disableScreenUpdatesUntilFlush]; |
| |
| // Make sure any layer changes that happened as a result of layout |
| // via -viewWillDraw are committed. |
| [CATransaction flush]; |
| [webView _setNeedsOneShotDrawingSynchronization:NO]; |
| } |
| #endif |
| |
| if (webView) |
| CallUIDelegate(webView, @selector(webView:didDrawFrame:), [self _frame]); |
| } |
| |
| #if PLATFORM(MAC) |
| |
| // Turn off the additional clip while computing our visibleRect. |
| - (NSRect)visibleRect |
| { |
| if (!([[self superview] isKindOfClass:[WebClipView class]])) |
| return [super visibleRect]; |
| |
| WebClipView *clipView = (WebClipView *)[self superview]; |
| |
| if (![clipView hasAdditionalClip]) |
| return [super visibleRect]; |
| |
| NSRect additionalClip = [clipView additionalClip]; |
| [clipView resetAdditionalClip]; |
| NSRect visibleRect = [super visibleRect]; |
| [clipView setAdditionalClip:additionalClip]; |
| return visibleRect; |
| } |
| |
| - (void)_invalidateGStatesForTree |
| { |
| // AppKit is in the process of traversing the NSView tree, and is going to send -renewGState to |
| // descendants, including plug-in views. This can result in calls out to plug-in code and back into |
| // WebCore via JavaScript, which could normally mutate the NSView tree while it is being traversed. |
| // Defer those mutations while descendants are being traveresed. |
| WebCore::WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates; |
| [super _invalidateGStatesForTree]; |
| } |
| |
| - (BOOL)isFlipped |
| { |
| return YES; |
| } |
| |
| - (void)windowDidBecomeKey:(NSNotification *)notification |
| { |
| if (!pthread_main_np()) { |
| [self performSelectorOnMainThread:_cmd withObject:notification waitUntilDone:NO]; |
| return; |
| } |
| |
| NSWindow *keyWindow = [notification object]; |
| |
| if (keyWindow == [self window]) |
| [self _updateSecureInputState]; |
| } |
| |
| - (void)windowDidResignKey:(NSNotification *)notification |
| { |
| if (!pthread_main_np()) { |
| [self performSelectorOnMainThread:_cmd withObject:notification waitUntilDone:NO]; |
| return; |
| } |
| |
| NSWindow *formerKeyWindow = [notification object]; |
| |
| if (formerKeyWindow == [self window] || formerKeyWindow == [[self window] attachedSheet]) { |
| [self _updateSecureInputState]; |
| [_private->completionController endRevertingChange:NO moveLeft:NO]; |
| } |
| } |
| |
| - (void)windowWillClose:(NSNotification *)notification |
| { |
| if (!pthread_main_np()) { |
| [self performSelectorOnMainThread:_cmd withObject:notification waitUntilDone:NO]; |
| return; |
| } |
| |
| [_private->completionController endRevertingChange:NO moveLeft:NO]; |
| [[self _pluginController] destroyAllPlugins]; |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| - (void)scrollWheel:(WebEvent *)event |
| { |
| #if PLATFORM(MAC) |
| // 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. |
| // FIXME: Why not do this on iOS too? |
| retainPtr(event).autorelease(); |
| #endif |
| |
| auto* frame = core([self _frame]); |
| if (!frame || !frame->eventHandler().wheelEvent(event)) { |
| #if PLATFORM(MAC) |
| [super scrollWheel:event]; |
| #endif |
| } |
| |
| #if PLATFORM(MAC) |
| [[[self _webView] _immediateActionController] webView:[self _webView] didHandleScrollWheel:event]; |
| #endif |
| } |
| |
| - (BOOL)_isSelectionEvent:(WebEvent *)event |
| { |
| #if PLATFORM(MAC) |
| bool allowShadowContent = true; |
| #else |
| bool allowShadowContent = false; // FIXME: Why does this need to be false on iOS? |
| #endif |
| |
| NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil]; |
| return [[[self elementAtPoint:point allowShadowContent:allowShadowContent] objectForKey:WebElementIsSelectedKey] boolValue]; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (BOOL)_isScrollBarEvent:(NSEvent *)event |
| { |
| NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil]; |
| return [[[self elementAtPoint:point allowShadowContent:YES] objectForKey:WebElementIsInScrollBarKey] boolValue]; |
| } |
| |
| - (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. |
| retainPtr(event).autorelease(); |
| |
| NSView *hitView = [self _hitViewForEvent:event]; |
| WebHTMLView *hitHTMLView = [hitView isKindOfClass:[self class]] ? (WebHTMLView *)hitView : nil; |
| |
| if (hitHTMLView) { |
| bool result = false; |
| if (auto* coreFrame = core([hitHTMLView _frame])) { |
| coreFrame->eventHandler().setActivationEventNumber([event eventNumber]); |
| [hitHTMLView _setMouseDownEvent:event]; |
| if ([hitHTMLView _isSelectionEvent:event]) { |
| #if ENABLE(DRAG_SUPPORT) |
| if (auto* page = coreFrame->page()) |
| result = coreFrame->eventHandler().eventMayStartDrag(WebCore::PlatformEventFactory::createPlatformMouseEvent(event, [[self _webView] _pressureEvent], page->chrome().platformPageClient())); |
| #endif |
| } else if ([hitHTMLView _isScrollBarEvent:event]) |
| result = true; |
| [hitHTMLView _setMouseDownEvent:nil]; |
| } |
| return result; |
| } |
| return [hitView acceptsFirstMouse:event]; |
| } |
| |
| - (BOOL)shouldDelayWindowOrderingForEvent:(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. |
| retainPtr(event).autorelease(); |
| |
| NSView *hitView = [self _hitViewForEvent:event]; |
| WebHTMLView *hitHTMLView = [hitView isKindOfClass:[self class]] ? (WebHTMLView *)hitView : nil; |
| if (hitHTMLView) { |
| bool result = false; |
| if ([hitHTMLView _isSelectionEvent:event]) { |
| [hitHTMLView _setMouseDownEvent:event]; |
| #if ENABLE(DRAG_SUPPORT) |
| if (auto* coreFrame = core([hitHTMLView _frame])) { |
| if (auto* page = coreFrame->page()) |
| result = coreFrame->eventHandler().eventMayStartDrag(WebCore::PlatformEventFactory::createPlatformMouseEvent(event, [[self _webView] _pressureEvent], page->chrome().platformPageClient())); |
| } |
| #endif |
| [hitHTMLView _setMouseDownEvent:nil]; |
| } |
| return result; |
| } |
| return [hitView shouldDelayWindowOrderingForEvent:event]; |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| - (void)mouseDown:(WebEvent *)event |
| { |
| [[self _webView] prepareForMouseDown]; |
| |
| #if PLATFORM(MAC) |
| // 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. |
| retainPtr(event).autorelease(); |
| #endif |
| |
| RetainPtr<WebHTMLView> protector = self; |
| |
| #if PLATFORM(MAC) |
| if ([[self inputContext] wantsToHandleMouseEvents] && [[self inputContext] handleMouseEvent:event]) |
| return; |
| #endif |
| |
| _private->handlingMouseDownEvent = YES; |
| |
| // Record the mouse down position so we can determine drag hysteresis. |
| [self _setMouseDownEvent:event]; |
| |
| #if PLATFORM(IOS_FAMILY) |
| // TEXTINPUT: if there is marked text and the current input |
| // manager wants to handle mouse events, we need to make sure to |
| // pass it to them. If not, then we need to notify the input |
| // manager when the marked text is abandoned (user clicks outside |
| // the marked area) |
| _private->ignoringMouseDraggedEvents = NO; |
| |
| // Let WebCore get a chance to deal with the event. This will call back to us |
| // to start the autoscroll timer if appropriate. |
| if (auto* coreFrame = core([self _frame])) |
| coreFrame->eventHandler().mouseDown(event); |
| #else |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| NSInputManager *currentInputManager = [NSInputManager currentInputManager]; |
| |
| if (![currentInputManager wantsToHandleMouseEvents] || ![currentInputManager handleMouseEvent:event]) { |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| [_private->completionController endRevertingChange:NO moveLeft:NO]; |
| |
| // If the web page handles the context menu event and menuForEvent: returns nil, we'll get control click events here. |
| // We don't want to pass them along to KHTML a second time. |
| if (!([event modifierFlags] & NSEventModifierFlagControl)) { |
| _private->ignoringMouseDraggedEvents = NO; |
| |
| // Let WebCore get a chance to deal with the event. This will call back to us |
| // to start the autoscroll timer if appropriate. |
| if (auto* coreFrame = core([self _frame])) |
| coreFrame->eventHandler().mouseDown(event, [[self _webView] _pressureEvent]); |
| } |
| } |
| #endif |
| |
| _private->handlingMouseDownEvent = NO; |
| } |
| |
| #if ENABLE(TOUCH_EVENTS) |
| |
| - (void)touch:(WebEvent *)event |
| { |
| RetainPtr<WebHTMLView> protector = self; |
| |
| // Let WebCore get a chance to deal with the event. This will call back to us |
| // to start the autoscroll timer if appropriate. |
| if (auto* coreFrame = core([self _frame])) |
| coreFrame->eventHandler().touchEvent(event); |
| } |
| |
| #endif |
| |
| #if ENABLE(DRAG_SUPPORT) && PLATFORM(MAC) |
| |
| static NSDragOperation kit(OptionSet<WebCore::DragOperation> operationMask) |
| { |
| NSDragOperation result = NSDragOperationNone; |
| if (operationMask.contains(WebCore::DragOperation::Copy)) |
| result |= NSDragOperationCopy; |
| if (operationMask.contains(WebCore::DragOperation::Link)) |
| result |= NSDragOperationLink; |
| if (operationMask.contains(WebCore::DragOperation::Generic)) |
| result |= NSDragOperationGeneric; |
| if (operationMask.contains(WebCore::DragOperation::Private)) |
| result |= NSDragOperationPrivate; |
| if (operationMask.contains(WebCore::DragOperation::Move)) |
| result |= NSDragOperationMove; |
| if (operationMask.contains(WebCore::DragOperation::Delete)) |
| result |= NSDragOperationDelete; |
| return result; |
| } |
| |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN |
| - (void)dragImage:(NSImage *)dragImage |
| at:(NSPoint)at |
| offset:(NSSize)offset |
| event:(NSEvent *)event |
| pasteboard:(NSPasteboard *)pasteboard |
| source:(id)source |
| slideBack:(BOOL)slideBack |
| IGNORE_WARNINGS_END |
| { |
| ASSERT(self == [self _topHTMLView]); |
| [pasteboard setString:@"" forType:[WebHTMLView _dummyPasteboardType]]; |
| |
| [super dragImage:dragImage at:at offset:offset event:event pasteboard:pasteboard source:source slideBack:slideBack]; |
| } |
| |
| - (void)mouseDragged:(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. |
| retainPtr(event).autorelease(); |
| |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| NSInputManager *currentInputManager = [NSInputManager currentInputManager]; |
| if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event]) |
| return; |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| |
| [self retain]; |
| |
| if (!_private->ignoringMouseDraggedEvents) { |
| if (auto* frame = core([self _frame])) { |
| if (auto* page = frame->page()) |
| page->mainFrame().eventHandler().mouseDragged(event, [[self _webView] _pressureEvent]); |
| } |
| } |
| |
| [self release]; |
| } |
| |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN |
| - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_END |
| { |
| ASSERT(![self _webView] || [self _isTopHTMLView]); |
| |
| auto* page = core([self _webView]); |
| if (!page) |
| return NSDragOperationNone; |
| |
| return kit(page->dragController().sourceDragOperationMask()); |
| } |
| |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN |
| - (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_END |
| { |
| ASSERT(![self _webView] || [self _isTopHTMLView]); |
| |
| NSPoint windowImageLoc = [[self window] convertScreenToBase:aPoint]; |
| NSPoint windowMouseLoc = windowImageLoc; |
| |
| if (auto* page = core([self _webView])) { |
| windowMouseLoc = NSMakePoint(windowImageLoc.x + page->dragController().dragOffset().x(), windowImageLoc.y + page->dragController().dragOffset().y()); |
| page->dragController().dragEnded(); |
| } |
| |
| [[self _frame] _dragSourceEndedAt:windowMouseLoc operation:operation]; |
| |
| // Prevent queued mouseDragged events from coming after the drag and fake mouseUp event. |
| _private->ignoringMouseDraggedEvents = YES; |
| |
| // Once the dragging machinery kicks in, we no longer get mouse drags or the up event. |
| // WebCore expects to get balanced down/up's, so we must fake up a mouseup. |
| NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSEventTypeLeftMouseUp |
| location:windowMouseLoc |
| modifierFlags:[[NSApp currentEvent] modifierFlags] |
| timestamp:[NSDate timeIntervalSinceReferenceDate] |
| windowNumber:[[self window] windowNumber] |
| context:nullptr |
| eventNumber:0 clickCount:0 pressure:0]; |
| [self mouseUp:fakeEvent]; // This will also update the mouseover state. |
| } |
| |
| static bool matchesExtensionOrEquivalent(NSString *filename, NSString *extension) |
| { |
| NSString *extensionAsSuffix = [@"." stringByAppendingString:extension]; |
| return [filename _webkit_hasCaseInsensitiveSuffix:extensionAsSuffix] |
| || ([extension _webkit_isCaseInsensitiveEqualToString:@"jpeg"] |
| && [filename _webkit_hasCaseInsensitiveSuffix:@".jpg"]); |
| } |
| |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN |
| - (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_END |
| { |
| RetainPtr<NSFileWrapper> wrapper; |
| NSURL *draggingElementURL = nil; |
| |
| if (auto tiffResource = _private->promisedDragTIFFDataSource) { |
| if (auto* buffer = tiffResource->resourceBuffer()) { |
| NSURLResponse *response = tiffResource->response().nsURLResponse(); |
| draggingElementURL = [response URL]; |
| wrapper = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:buffer->makeContiguous()->createNSData().get()]); |
| NSString* filename = [response suggestedFilename]; |
| NSString* trueExtension(tiffResource->image()->filenameExtension()); |
| if (!matchesExtensionOrEquivalent(filename, trueExtension)) |
| filename = [[filename stringByAppendingString:@"."] stringByAppendingString:trueExtension]; |
| [wrapper setPreferredFilename:filename]; |
| } |
| } |
| |
| if (!wrapper) { |
| ASSERT(![self _webView] || [self _isTopHTMLView]); |
| auto* page = core([self _webView]); |
| |
| //If a load occurs midway through a drag, the view may be detached, which gives |
| //us no ability to get to the original Page, so we cannot access any drag state |
| //FIXME: is there a way to recover? |
| if (!page) |
| return nil; |
| |
| const URL& imageURL = page->dragController().draggingImageURL(); |
| if (!imageURL.isEmpty()) |
| draggingElementURL = imageURL; |
| |
| wrapper = [[self _dataSource] _fileWrapperForURL:draggingElementURL]; |
| } |
| |
| if (wrapper == nil) { |
| 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 = [[NSFileManager defaultManager] _webkit_pathWithUniqueFilenameForPath:path]; |
| if (![wrapper writeToURL:[NSURL fileURLWithPath:path isDirectory:NO] options:NSFileWrapperWritingWithNameUpdating originalContentsURL:nil error:nullptr]) |
| LOG_ERROR("Failed to create image file via -[NSFileWrapper writeToURL:options:originalContentsURL:error:]"); |
| |
| if (draggingElementURL) |
| [[NSFileManager defaultManager] _webkit_setMetadataURL:[draggingElementURL absoluteString] referrer:nil atPath:path]; |
| |
| return @[[path lastPathComponent]]; |
| } |
| |
| // MARK: NSDraggingSource |
| |
| - (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context |
| { |
| ASSERT(![self _webView] || [self _isTopHTMLView]); |
| |
| auto* page = core([self _webView]); |
| if (!page) |
| return NSDragOperationNone; |
| |
| return kit(page->dragController().sourceDragOperationMask()); |
| } |
| |
| - (void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation |
| { |
| ASSERT(![self _webView] || [self _isTopHTMLView]); |
| |
| NSPoint windowLocation = [self.window convertRectFromScreen:{ screenPoint, NSZeroSize }].origin; |
| |
| if (auto* page = core([self _webView])) |
| page->dragController().dragEnded(); |
| |
| [[self _frame] _dragSourceEndedAt:windowLocation operation:operation]; |
| |
| // Prevent queued mouseDragged events from coming after the drag and fake mouseUp event. |
| _private->ignoringMouseDraggedEvents = YES; |
| |
| // Once the dragging machinery kicks in, we no longer get mouse drags or the up event. |
| // WebCore expects to get balanced down/up's, so we must fake up a mouseup. |
| NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSEventTypeLeftMouseUp location:windowLocation modifierFlags:[NSApp currentEvent].modifierFlags timestamp:[NSDate timeIntervalSinceReferenceDate] windowNumber:self.window. windowNumber context:nullptr eventNumber:0 clickCount:0 pressure:0]; |
| |
| // This will also update the mouseover state. |
| [self mouseUp:fakeEvent]; |
| } |
| |
| #endif // ENABLE(DRAG_SUPPORT) && PLATFORM(MAC) |
| |
| - (void)mouseUp:(WebEvent *)event |
| { |
| [[self _webView] prepareForMouseUp]; |
| |
| #if PLATFORM(MAC) |
| // 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. |
| retainPtr(event).autorelease(); |
| #endif |
| |
| [self _setMouseDownEvent:nil]; |
| |
| #if PLATFORM(MAC) |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| NSInputManager *currentInputManager = [NSInputManager currentInputManager]; |
| if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event]) |
| return; |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| #endif |
| |
| [self retain]; |
| |
| [self _stopAutoscrollTimer]; |
| if (auto* frame = core([self _frame])) { |
| if (auto* page = frame->page()) { |
| #if PLATFORM(IOS_FAMILY) |
| page->mainFrame().eventHandler().mouseUp(event); |
| #else |
| page->mainFrame().eventHandler().mouseUp(event, [[self _webView] _pressureEvent]); |
| #endif |
| } |
| } |
| |
| #if PLATFORM(MAC) |
| [self _updateMouseoverWithFakeEvent]; |
| #endif |
| |
| [self release]; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (void)mouseMoved:(NSEvent *)event |
| { |
| [self _updateMouseoverWithEvent:event]; |
| } |
| |
| #endif |
| |
| - (void)pressureChangeWithEvent:(NSEvent *)event |
| { |
| #if PLATFORM(MAC) |
| NSEvent *lastPressureEvent = [[self _webView] _pressureEvent]; |
| if (event.phase != NSEventPhaseChanged && event.phase != NSEventPhaseBegan && event.phase != NSEventPhaseEnded) |
| return; |
| |
| RefPtr<WebCore::Frame> coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return; |
| |
| coreFrame->eventHandler().pressureChange(event, lastPressureEvent); |
| [[self _webView] _setPressureEvent:event]; |
| #endif |
| } |
| |
| #if PLATFORM(MAC) |
| |
| // returning YES from this method is the way we tell AppKit that it is ok for this view |
| // to be in the key loop even when "tab to all controls" is not on. |
| - (BOOL)needsPanelToBecomeKey |
| { |
| return YES; |
| } |
| |
| #endif |
| |
| // Utility function to make sure we don't return anything through the NSTextInput |
| // API when an editable region is not currently focused. |
| static BOOL isTextInput(WebCore::Frame* coreFrame) |
| { |
| if (!coreFrame) |
| return NO; |
| const auto& selection = coreFrame->selection().selection(); |
| return !selection.isNone() && selection.isContentEditable(); |
| } |
| |
| #if PLATFORM(MAC) |
| |
| static BOOL isInPasswordField(WebCore::Frame* coreFrame) |
| { |
| return coreFrame && coreFrame->selection().selection().isInPasswordField(); |
| } |
| |
| #endif |
| |
| static RefPtr<WebCore::KeyboardEvent> currentKeyboardEvent(WebCore::Frame* coreFrame) |
| { |
| #if PLATFORM(MAC) |
| NSEvent *event = [NSApp currentEvent]; |
| if (!event) |
| return nullptr; |
| |
| switch ([event type]) { |
| case NSEventTypeKeyDown: { |
| WebCore::PlatformKeyboardEvent platformEvent = WebCore::PlatformEventFactory::createPlatformKeyboardEvent(event); |
| platformEvent.disambiguateKeyDownEvent(WebCore::PlatformEvent::RawKeyDown); |
| return WebCore::KeyboardEvent::create(platformEvent, &coreFrame->windowProxy()); |
| } |
| case NSEventTypeKeyUp: |
| return WebCore::KeyboardEvent::create(WebCore::PlatformEventFactory::createPlatformKeyboardEvent(event), &coreFrame->windowProxy()); |
| default: |
| return nullptr; |
| } |
| #else |
| WebEvent *event = [WAKWindow currentEvent]; |
| if (!event) |
| return nullptr; |
| WebEventType type = event.type; |
| if (type == WebEventKeyDown || type == WebEventKeyUp) { |
| auto* document = coreFrame->document(); |
| return WebCore::KeyboardEvent::create(WebCore::PlatformEventFactory::createPlatformKeyboardEvent(event), document ? document->windowProxy() : 0); |
| } |
| return nullptr; |
| #endif |
| } |
| |
| - (BOOL)becomeFirstResponder |
| { |
| NSSelectionDirection direction = NSDirectSelection; |
| if (![[self _webView] _isPerformingProgrammaticFocus]) |
| direction = [[self window] keyViewSelectionDirection]; |
| |
| #if PLATFORM(MAC) |
| [self _updateFontPanel]; |
| #endif |
| |
| auto* frame = core([self _frame]); |
| if (!frame) |
| return YES; |
| |
| #if PLATFORM(MAC) |
| BOOL exposeInputContext = isTextInput(frame) && !isInPasswordField(frame); |
| if (exposeInputContext != _private->exposeInputContext) { |
| _private->exposeInputContext = exposeInputContext; |
| [NSApp updateWindows]; |
| } |
| |
| _private->_forceUpdateSecureInputState = YES; |
| [self _updateSecureInputState]; |
| _private->_forceUpdateSecureInputState = NO; |
| #endif |
| |
| // FIXME: Kill ring handling is mostly in WebCore, so this call should also be moved there. |
| frame->editor().setStartNewKillRingSequence(true); |
| |
| auto* page = frame->page(); |
| if (!page) |
| return YES; |
| |
| if (![[self _webView] _isPerformingProgrammaticFocus]) |
| page->focusController().setFocusedFrame(frame); |
| |
| page->focusController().setFocused(true); |
| |
| if (direction == NSDirectSelection) |
| return YES; |
| |
| if (auto* document = frame->document()) |
| document->setFocusedElement(0); |
| page->focusController().setInitialFocus(direction == NSSelectingNext ? WebCore::FocusDirection::Forward : WebCore::FocusDirection::Backward, |
| currentKeyboardEvent(frame).get()); |
| return YES; |
| } |
| |
| - (BOOL)resignFirstResponder |
| { |
| BOOL resign = [super resignFirstResponder]; |
| if (resign) { |
| #if PLATFORM(MAC) |
| if (_private->isInSecureInputState) { |
| DisableSecureEventInput(); |
| _private->isInSecureInputState = NO; |
| } |
| [_private->completionController endRevertingChange:NO moveLeft:NO]; |
| #endif |
| auto* coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return resign; |
| |
| #if PLATFORM(IOS_FAMILY) |
| if (auto* document = coreFrame->document()) { |
| document->markers().removeMarkers(WebCore::DocumentMarker::DictationPhraseWithAlternatives); |
| document->markers().removeMarkers(WebCore::DocumentMarker::DictationResult); |
| } |
| #endif |
| |
| auto* page = coreFrame->page(); |
| if (!page) |
| return resign; |
| if (![self maintainsInactiveSelection]) { |
| [self deselectAll]; |
| if (![[self _webView] _isPerformingProgrammaticFocus]) |
| [self clearFocus]; |
| } |
| |
| id nextResponder = [[self window] _newFirstResponderAfterResigning]; |
| bool nextResponderIsInWebView = [nextResponder isKindOfClass:[NSView class]] |
| && [nextResponder isDescendantOf:[[[self _webView] mainFrame] frameView]]; |
| if (!nextResponderIsInWebView && ![[self _webView] _isPerformingProgrammaticFocus]) |
| page->focusController().setFocused(false); |
| } |
| return resign; |
| } |
| |
| - (void)setDataSource:(WebDataSource *)dataSource |
| { |
| ASSERT(dataSource); |
| if (_private->dataSource == dataSource) |
| return; |
| |
| ASSERT(!_private->closed); |
| |
| _private->dataSource = dataSource; |
| [_private->pluginController setDataSource:dataSource]; |
| |
| #if PLATFORM(MAC) |
| if (!_private->installedTrackingArea) { |
| NSTrackingAreaOptions options = NSTrackingMouseMoved | NSTrackingMouseEnteredAndExited | NSTrackingInVisibleRect | NSTrackingCursorUpdate; |
| if (_NSRecommendedScrollerStyle() == NSScrollerStyleLegacy) |
| options |= NSTrackingActiveAlways; |
| else |
| options |= NSTrackingActiveInKeyWindow; |
| |
| [self addTrackingArea:adoptNS([[NSTrackingArea alloc] initWithRect:[self frame] options:options owner:self userInfo:nil]).get()]; |
| _private->installedTrackingArea = YES; |
| } |
| #endif |
| } |
| |
| - (void)dataSourceUpdated:(WebDataSource *)dataSource |
| { |
| } |
| |
| #if PLATFORM(MAC) |
| |
| // This is an override of an NSControl method that wants to repaint the entire view when the window resigns/becomes key. |
| // WebHTMLView is an NSControl only because it hosts NSCells that are painted by WebCore's Aqua theme |
| // renderer (and those cells must be hosted by an enclosing NSControl in order to paint properly). |
| - (void)updateCell:(NSCell *)cell |
| { |
| } |
| |
| #endif |
| |
| // Does setNeedsDisplay:NO as a side effect when printing is ending. |
| // pageWidth != 0 implies we will relayout to a new width |
| - (void)_setPrinting:(BOOL)printing minimumPageLogicalWidth:(float)minPageLogicalWidth logicalHeight:(float)minPageLogicalHeight originalPageWidth:(float)originalPageWidth originalPageHeight:(float)originalPageHeight maximumShrinkRatio:(float)maximumShrinkRatio adjustViewSize:(BOOL)adjustViewSize paginateScreenContent:(BOOL)paginateScreenContent |
| { |
| if (printing == _private->printing && paginateScreenContent == _private->paginateScreenContent) |
| return; |
| |
| for (WebFrame *subframe in [[self _frame] childFrames]) { |
| WebFrameView *frameView = [subframe frameView]; |
| if ([[subframe _dataSource] _isDocumentHTML]) { |
| [(WebHTMLView *)[frameView documentView] _setPrinting:printing minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:adjustViewSize paginateScreenContent:paginateScreenContent]; |
| } |
| } |
| |
| _private->pageRects = nil; |
| _private->printing = printing; |
| _private->paginateScreenContent = paginateScreenContent; |
| |
| auto* coreFrame = core([self _frame]); |
| if (coreFrame) { |
| if (auto* coreView = coreFrame->view()) |
| coreView->setMediaType(_private->printing ? "print"_s : "screen"_s); |
| if (auto* document = coreFrame->document()) { |
| // In setting printing, we should not validate resources already cached for the document. |
| // See https://bugs.webkit.org/show_bug.cgi?id=43704 |
| WebCore::ResourceCacheValidationSuppressor validationSuppressor(document->cachedResourceLoader()); |
| |
| document->setPaginatedForScreen(_private->paginateScreenContent); |
| document->setPrinting(_private->printing); |
| document->styleScope().didChangeStyleSheetEnvironment(); |
| } |
| } |
| |
| [self setNeedsLayout:YES]; |
| [self layoutToMinimumPageWidth:minPageLogicalWidth height:minPageLogicalHeight originalPageWidth:originalPageWidth originalPageHeight:originalPageHeight maximumShrinkRatio:maximumShrinkRatio adjustingViewSize:adjustViewSize]; |
| if (!printing) { |
| // Can't do this when starting printing or nested printing won't work, see 3491427. |
| [self setNeedsDisplay:NO]; |
| } |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (BOOL)canPrintHeadersAndFooters |
| { |
| return YES; |
| } |
| |
| // This is needed for the case where the webview is embedded in the view that's being printed. |
| // It shouldn't be called when the webview is being printed directly. |
| - (void)adjustPageHeightNew:(CGFloat *)newBottom top:(CGFloat)oldTop bottom:(CGFloat)oldBottom limit:(CGFloat)bottomLimit |
| { |
| // This helps when we print as part of a larger print process. |
| // If the WebHTMLView itself is what we're printing, then we will never have to do this. |
| BOOL wasInPrintingMode = _private->printing; |
| if (!wasInPrintingMode) |
| [self _setPrinting:YES minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:NO paginateScreenContent:[self _isInScreenPaginationMode]]; |
| |
| *newBottom = [self _adjustedBottomOfPageWithTop:oldTop bottom:oldBottom limit:bottomLimit]; |
| |
| if (!wasInPrintingMode) { |
| NSPrintOperation *currenPrintOperation = [NSPrintOperation currentOperation]; |
| if (currenPrintOperation) |
| // delay _setPrinting:NO until back to main loop as this method may get called repeatedly |
| [self performSelector:@selector(_delayedEndPrintMode:) withObject:currenPrintOperation afterDelay:0]; |
| else |
| // not sure if this is actually ever invoked, it probably shouldn't be |
| [self _setPrinting:NO minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:NO paginateScreenContent:[self _isInScreenPaginationMode]]; |
| } |
| } |
| |
| - (float)_scaleFactorForPrintOperation:(NSPrintOperation *)printOperation |
| { |
| bool useViewWidth = true; |
| auto* coreFrame = core([self _frame]); |
| if (coreFrame) { |
| auto* document = coreFrame->document(); |
| if (document && document->renderView()) |
| useViewWidth = document->renderView()->style().isHorizontalWritingMode(); |
| } |
| |
| float viewLogicalWidth = useViewWidth ? NSWidth([self bounds]) : NSHeight([self bounds]); |
| if (viewLogicalWidth < 1) { |
| LOG_ERROR("%@ has no logical width when printing", self); |
| return 1.0f; |
| } |
| |
| float userScaleFactor = [printOperation _web_pageSetupScaleFactor]; |
| float maxShrinkToFitScaleFactor = 1.0f / _WebHTMLViewPrintingMaximumShrinkFactor; |
| float shrinkToFitScaleFactor = (useViewWidth ? [printOperation _web_availablePaperWidth] : [printOperation _web_availablePaperHeight]) / viewLogicalWidth; |
| return userScaleFactor * std::max(maxShrinkToFitScaleFactor, shrinkToFitScaleFactor); |
| } |
| |
| // FIXME 3491344: This is a secret AppKit-internal method that we need to override in order |
| // to get our shrink-to-fit to work with a custom pagination scheme. We can do this better |
| // if AppKit makes it SPI/API. |
| - (CGFloat)_provideTotalScaleFactorForPrintOperation:(NSPrintOperation *)printOperation |
| { |
| return [self _scaleFactorForPrintOperation:printOperation]; |
| } |
| |
| // This is used for Carbon printing. At some point we might want to make this public API. |
| - (void)setPageWidthForPrinting:(float)pageWidth |
| { |
| [self _setPrinting:NO minimumPageLogicalWidth:0 logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:0 adjustViewSize:NO paginateScreenContent:[self _isInScreenPaginationMode]]; |
| [self _setPrinting:YES minimumPageLogicalWidth:pageWidth logicalHeight:0 originalPageWidth:0 originalPageHeight:0 maximumShrinkRatio:1 adjustViewSize:YES paginateScreenContent:[self _isInScreenPaginationMode]]; |
| } |
| |
| - (void)_endPrintModeAndRestoreWindowAutodisplay |
| { |
| [self _endPrintMode]; |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| [[self window] setAutodisplay:YES]; |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| } |
| |
| - (void)_delayedEndPrintMode:(NSPrintOperation *)initiatingOperation |
| { |
| ASSERT_ARG(initiatingOperation, initiatingOperation != nil); |
| NSPrintOperation *currentOperation = [NSPrintOperation currentOperation]; |
| if (initiatingOperation == currentOperation) { |
| // The print operation is still underway. We don't expect this to ever happen, hence the assert, but we're |
| // being extra paranoid here since the printing code is so fragile. Delay the cleanup |
| // further. |
| ASSERT_NOT_REACHED(); |
| [self performSelector:@selector(_delayedEndPrintMode:) withObject:initiatingOperation afterDelay:0]; |
| } else if ([currentOperation view] == self) { |
| // A new print job has started, but it is printing the same WebHTMLView again. We don't expect |
| // this to ever happen, hence the assert, but we're being extra paranoid here since the printing code is so |
| // fragile. Do nothing, because we don't want to break the print job currently in progress, and |
| // the print job currently in progress is responsible for its own cleanup. |
| ASSERT_NOT_REACHED(); |
| } else { |
| // The print job that kicked off this delayed call has finished, and this view is not being |
| // printed again. We expect that no other print job has started. Since this delayed call wasn't |
| // cancelled, beginDocument and endDocument must not have been called, and we need to clean up |
| // the print mode here. |
| ASSERT(currentOperation == nil); |
| [self _endPrintModeAndRestoreWindowAutodisplay]; |
| } |
| } |
| |
| // Return the number of pages available for printing |
| - (BOOL)knowsPageRange:(NSRangePointer)range |
| { |
| // Must do this explicit display here, because otherwise the view might redisplay while the print |
| // sheet was up, using printer fonts (and looking different). |
| [self displayIfNeeded]; |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| [[self window] setAutodisplay:NO]; |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| |
| [[self _webView] _adjustPrintingMarginsForHeaderAndFooter]; |
| NSPrintOperation *printOperation = [NSPrintOperation currentOperation]; |
| if (![self _beginPrintModeWithPageWidth:[printOperation _web_availablePaperWidth] height:[printOperation _web_availablePaperHeight] shrinkToFit:YES]) |
| return NO; |
| |
| // Certain types of errors, including invalid page ranges, can cause beginDocument and |
| // endDocument to be skipped after we've put ourselves in print mode (see 4145905). In those cases |
| // we need to get out of print mode without relying on any more callbacks from the printing mechanism. |
| // If we get as far as beginDocument without trouble, then this delayed request will be cancelled. |
| // If not cancelled, this delayed call will be invoked in the next pass through the main event loop, |
| // which is after beginDocument and endDocument would be called. |
| [self performSelector:@selector(_delayedEndPrintMode:) withObject:printOperation afterDelay:0]; |
| |
| // There is a theoretical chance that someone could do some drawing between here and endDocument, |
| // if something caused setNeedsDisplay after this point. If so, it's not a big tragedy, because |
| // you'd simply see the printer fonts on screen. As of this writing, this does not happen with Safari. |
| |
| range->location = 1; |
| float totalScaleFactor = [self _scaleFactorForPrintOperation:printOperation]; |
| float userScaleFactor = [printOperation _web_pageSetupScaleFactor]; |
| float fullPageWidth = floorf([printOperation _web_availablePaperWidth] / totalScaleFactor); |
| float fullPageHeight = floorf([printOperation _web_availablePaperHeight] / totalScaleFactor); |
| WebFrame *frame = [self _frame]; |
| NSArray *newPageRects = [frame _computePageRectsWithPrintScaleFactor:userScaleFactor pageSize:NSMakeSize(fullPageWidth, fullPageHeight)]; |
| |
| // AppKit gets all messed up if you give it a zero-length page count (see 3576334), so if we |
| // hit that case we'll pass along a degenerate 1 pixel square to print. This will print |
| // a blank page (with correct-looking header and footer if that option is on), which matches |
| // the behavior of IE and Camino at least. |
| if ([newPageRects count] == 0) |
| newPageRects = @[[NSValue valueWithRect:NSMakeRect(0, 0, 1, 1)]]; |
| |
| _private->pageRects = newPageRects; |
| |
| range->length = [_private->pageRects count]; |
| |
| return YES; |
| } |
| |
| // Return the drawing rectangle for a particular page number |
| - (NSRect)rectForPage:(NSInteger)page |
| { |
| return [[_private->pageRects objectAtIndex:page - 1] rectValue]; |
| } |
| |
| - (void)drawPageBorderWithSize:(NSSize)borderSize |
| { |
| ASSERT(NSEqualSizes(borderSize, [[[NSPrintOperation currentOperation] printInfo] paperSize])); |
| [[self _webView] _drawHeaderAndFooter]; |
| } |
| |
| - (void)beginDocument |
| { |
| @try { |
| // From now on we'll get a chance to call _endPrintMode in either beginDocument or |
| // endDocument, so we can cancel the "just in case" pending call. |
| [NSObject cancelPreviousPerformRequestsWithTarget:self |
| selector:@selector(_delayedEndPrintMode:) |
| object:[NSPrintOperation currentOperation]]; |
| [super beginDocument]; |
| } @catch (NSException *localException) { |
| // Exception during [super beginDocument] means that endDocument will not get called, |
| // so we need to clean up our "print mode" here. |
| [self _endPrintModeAndRestoreWindowAutodisplay]; |
| } |
| } |
| |
| - (void)endDocument |
| { |
| [super endDocument]; |
| // Note sadly at this point [NSGraphicsContext currentContextDrawingToScreen] is still NO |
| [self _endPrintModeAndRestoreWindowAutodisplay]; |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| - (void)keyDown:(WebEvent *)event |
| { |
| #if PLATFORM(MAC) |
| // 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. |
| retainPtr(event).autorelease(); |
| #endif |
| |
| RetainPtr<WebHTMLView> selfProtector = self; |
| BOOL eventWasSentToWebCore = (_private->keyDownEvent == event); |
| |
| BOOL callSuper = NO; |
| |
| _private->keyDownEvent = event; |
| |
| #if PLATFORM(MAC) |
| BOOL completionPopupWasOpen = _private->completionController && [_private->completionController popupWindowIsOpen]; |
| auto* coreFrame = core([self _frame]); |
| if (!eventWasSentToWebCore && coreFrame && coreFrame->eventHandler().keyEvent(event)) { |
| // WebCore processed a key event, bail on any preexisting complete: UI |
| if (completionPopupWasOpen) |
| [_private->completionController endRevertingChange:YES moveLeft:NO]; |
| } else if (!_private->completionController || ![_private->completionController filterKeyDown:event]) { |
| // Not consumed by complete: popup window |
| [_private->completionController endRevertingChange:YES moveLeft:NO]; |
| callSuper = YES; |
| } |
| #else |
| auto* coreFrame = core([self _frame]); |
| if (!eventWasSentToWebCore && coreFrame) |
| coreFrame->eventHandler().keyEvent(event); |
| #endif |
| |
| if (callSuper) |
| [super keyDown:event]; |
| else { |
| #if PLATFORM(MAC) |
| [NSCursor setHiddenUntilMouseMoves:YES]; |
| #endif |
| } |
| } |
| |
| - (void)keyUp:(WebEvent *)event |
| { |
| #if PLATFORM(MAC) |
| // 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. |
| retainPtr(event).autorelease(); |
| #endif |
| |
| BOOL eventWasSentToWebCore = (_private->keyDownEvent == event); |
| |
| RetainPtr<WebHTMLView> selfProtector = self; |
| auto* coreFrame = core([self _frame]); |
| if (coreFrame && !eventWasSentToWebCore) |
| coreFrame->eventHandler().keyEvent(event); |
| else |
| [super keyUp:event]; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (void)flagsChanged:(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. |
| retainPtr(event).autorelease(); |
| |
| RetainPtr<WebHTMLView> selfProtector = self; |
| |
| auto* coreFrame = core([self _frame]); |
| unsigned short keyCode = [event keyCode]; |
| |
| // Don't make an event from the num lock and function keys. |
| if (coreFrame && keyCode != 0 && keyCode != 10 && keyCode != 63) { |
| coreFrame->eventHandler().keyEvent(WebCore::PlatformEventFactory::createPlatformKeyboardEvent(event)); |
| return; |
| } |
| |
| [super flagsChanged:event]; |
| } |
| |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN |
| - (id)accessibilityAttributeValue:(NSString*)attributeName |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_END |
| { |
| if ([attributeName isEqualToString: NSAccessibilityChildrenAttribute]) { |
| id accTree = [[self _frame] accessibilityRoot]; |
| if (accTree) |
| return @[accTree]; |
| return nil; |
| } |
| return [super accessibilityAttributeValue:attributeName]; |
| } |
| |
| #endif |
| |
| - (id)accessibilityFocusedUIElement |
| { |
| id accTree = [[self _frame] accessibilityRoot]; |
| if (accTree) |
| return [accTree accessibilityFocusedUIElement]; |
| return self; |
| } |
| |
| - (id)accessibilityHitTest:(NSPoint)point |
| { |
| id accTree = [[self _frame] accessibilityRoot]; |
| if (accTree) { |
| #if PLATFORM(IOS_FAMILY) |
| return [accTree accessibilityHitTest:point]; |
| #else |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| NSPoint windowCoord = [[self window] convertScreenToBase:point]; |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| return [accTree accessibilityHitTest:[self convertPoint:windowCoord fromView:nil]]; |
| #endif |
| } |
| return self; |
| } |
| |
| - (id)_accessibilityParentForSubview:(NSView *)subview |
| { |
| id accTree = [[self _frame] accessibilityRoot]; |
| if (!accTree) |
| return self; |
| id parent = [accTree _accessibilityParentForSubview:subview]; |
| if (!parent) |
| return self; |
| return parent; |
| } |
| |
| - (void)centerSelectionInVisibleArea:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| if (auto* coreFrame = core([self _frame])) |
| coreFrame->selection().revealSelection(WebCore::SelectionRevealMode::Reveal, WebCore::ScrollAlignment::alignCenterAlways); |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (NSData *)_selectionStartFontAttributesAsRTF |
| { |
| auto* coreFrame = core([self _frame]); |
| auto string = adoptNS([[NSAttributedString alloc] initWithString:@"x" |
| attributes:coreFrame ? coreFrame->editor().fontAttributesAtSelectionStart().createDictionary().get() : nil]); |
| return [string RTFFromRange:NSMakeRange(0, [string length]) documentAttributes:@{ }]; |
| } |
| |
| - (NSDictionary *)_fontAttributesFromFontPasteboard |
| { |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| NSPasteboard *fontPasteboard = [NSPasteboard pasteboardWithName:NSFontPboard]; |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| if (fontPasteboard == nil) |
| return nil; |
| NSData *data = [fontPasteboard dataForType:WebCore::legacyFontPasteboardType()]; |
| if (data == nil || [data length] == 0) |
| return nil; |
| // NSTextView does something more efficient by parsing the attributes only, but that's not available in API. |
| auto string = adoptNS([[NSAttributedString alloc] initWithRTF:data documentAttributes:NULL]); |
| if (string == nil || [string length] == 0) |
| return nil; |
| return [string fontAttributesInRange:NSMakeRange(0, 1)]; |
| } |
| |
| #endif |
| |
| - (DOMCSSStyleDeclaration *)_emptyStyle |
| { |
| return [[[self _frame] DOMDocument] createCSSStyleDeclaration]; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (NSString *)_colorAsString:(NSColor *)color |
| { |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| NSColor *rgbColor = [color colorUsingColorSpaceName:NSDeviceRGBColorSpace]; |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| // FIXME: If color is non-nil and rgbColor is nil, that means we got some kind |
| // of fancy color that can't be converted to RGB. Changing that to "transparent" |
| // might not be great, but it's probably OK. |
| if (rgbColor == nil) |
| return @"transparent"; |
| float r = [rgbColor redComponent]; |
| float g = [rgbColor greenComponent]; |
| float b = [rgbColor blueComponent]; |
| float a = [rgbColor alphaComponent]; |
| if (a == 0) |
| return @"transparent"; |
| if (r == 0 && g == 0 && b == 0 && a == 1) |
| return @"black"; |
| if (r == 1 && g == 1 && b == 1 && a == 1) |
| return @"white"; |
| // FIXME: Lots more named colors. Maybe we could use the table in WebCore? |
| if (a == 1) |
| return [NSString stringWithFormat:@"rgb(%.0f,%.0f,%.0f)", r * 255, g * 255, b * 255]; |
| return [NSString stringWithFormat:@"rgba(%.0f,%.0f,%.0f,%f)", r * 255, g * 255, b * 255, a]; |
| } |
| |
| - (NSString *)_shadowAsString:(NSShadow *)shadow |
| { |
| if (shadow == nil) |
| return @"none"; |
| NSSize offset = [shadow shadowOffset]; |
| float blurRadius = [shadow shadowBlurRadius]; |
| if (offset.width == 0 && offset.height == 0 && blurRadius == 0) |
| return @"none"; |
| NSColor *color = [shadow shadowColor]; |
| if (color == nil) |
| return @"none"; |
| // FIXME: Handle non-integral values here? |
| if (blurRadius == 0) |
| return [NSString stringWithFormat:@"%@ %.0fpx %.0fpx", [self _colorAsString:color], offset.width, offset.height]; |
| return [NSString stringWithFormat:@"%@ %.0fpx %.0fpx %.0fpx", [self _colorAsString:color], offset.width, offset.height, blurRadius]; |
| } |
| |
| - (DOMCSSStyleDeclaration *)_styleFromFontAttributes:(NSDictionary *)dictionary |
| { |
| DOMCSSStyleDeclaration *style = [self _emptyStyle]; |
| |
| NSColor *color = [dictionary objectForKey:NSBackgroundColorAttributeName]; |
| [style setBackgroundColor:[self _colorAsString:color]]; |
| |
| NSFont *font = [dictionary objectForKey:NSFontAttributeName]; |
| if (!font) { |
| [style setFontFamily:@"Helvetica"]; |
| [style setFontSize:@"12px"]; |
| [style setFontWeight:@"normal"]; |
| [style setFontStyle:@"normal"]; |
| } else { |
| NSFontManager *fm = [NSFontManager sharedFontManager]; |
| // FIXME: Need more sophisticated escaping code if we want to handle family names |
| // with characters like single quote or backslash in their names. |
| [style setFontFamily:[NSString stringWithFormat:@"'%@'", [font familyName]]]; |
| [style setFontSize:[NSString stringWithFormat:@"%0.fpx", [font pointSize]]]; |
| // FIXME: Map to the entire range of CSS weight values. |
| if ([fm weightOfFont:font] >= MIN_BOLD_WEIGHT) |
| [style setFontWeight:@"bold"]; |
| else |
| [style setFontWeight:@"normal"]; |
| if ([fm traitsOfFont:font] & NSItalicFontMask) |
| [style setFontStyle:@"italic"]; |
| else |
| [style setFontStyle:@"normal"]; |
| } |
| |
| color = [dictionary objectForKey:NSForegroundColorAttributeName]; |
| [style setColor:color ? [self _colorAsString:color] : (NSString *)@"black"]; |
| |
| NSShadow *shadow = [dictionary objectForKey:NSShadowAttributeName]; |
| [style setTextShadow:[self _shadowAsString:shadow]]; |
| |
| int strikethroughInt = [[dictionary objectForKey:NSStrikethroughStyleAttributeName] intValue]; |
| |
| int superscriptInt = [[dictionary objectForKey:NSSuperscriptAttributeName] intValue]; |
| if (superscriptInt > 0) |
| [style setVerticalAlign:@"super"]; |
| else if (superscriptInt < 0) |
| [style setVerticalAlign:@"sub"]; |
| else |
| [style setVerticalAlign:@"baseline"]; |
| int underlineInt = [[dictionary objectForKey:NSUnderlineStyleAttributeName] intValue]; |
| // FIXME: Underline wins here if we have both (see bug 3790443). |
| if (strikethroughInt == NSUnderlineStyleNone && underlineInt == NSUnderlineStyleNone) |
| [style setProperty:@"-webkit-text-decorations-in-effect" value:@"none" priority:@""]; |
| else if (underlineInt == NSUnderlineStyleNone) |
| [style setProperty:@"-webkit-text-decorations-in-effect" value:@"line-through" priority:@""]; |
| else |
| [style setProperty:@"-webkit-text-decorations-in-effect" value:@"underline" priority:@""]; |
| |
| return style; |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| - (void)_applyStyleToSelection:(DOMCSSStyleDeclaration *)style withUndoAction:(WebCore::EditAction)undoAction |
| { |
| [self _applyEditingStyleToSelection:WebCore::EditingStyle::create(core(style)) withUndoAction:undoAction]; |
| } |
| |
| - (void)_applyEditingStyleToSelection:(Ref<WebCore::EditingStyle>&&)editingStyle withUndoAction:(WebCore::EditAction)undoAction |
| { |
| if (auto* coreFrame = core([self _frame])) |
| coreFrame->editor().applyStyleToSelection(WTFMove(editingStyle), undoAction, WebCore::Editor::ColorFilterMode::InvertColor); |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (BOOL)_handleStyleKeyEquivalent:(NSEvent *)event |
| { |
| WebView *webView = [self _webView]; |
| if (!webView) |
| return NO; |
| |
| if (![[webView preferences] respectStandardStyleKeyEquivalents]) |
| return NO; |
| |
| if (![self _canEdit]) |
| return NO; |
| |
| if (([event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask) != NSEventModifierFlagCommand) |
| return NO; |
| |
| NSString *string = [event characters]; |
| if ([string caseInsensitiveCompare:@"b"] == NSOrderedSame) { |
| [self executeCoreCommandByName:"ToggleBold"]; |
| return YES; |
| } |
| if ([string caseInsensitiveCompare:@"i"] == NSOrderedSame) { |
| [self executeCoreCommandByName:"ToggleItalic"]; |
| return YES; |
| } |
| |
| return NO; |
| } |
| |
| - (BOOL)performKeyEquivalent:(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. |
| retainPtr(event).autorelease(); |
| |
| BOOL eventWasSentToWebCore = (_private->keyDownEvent == event); |
| BOOL ret = NO; |
| |
| _private->keyDownEvent = event; |
| |
| [self retain]; |
| |
| // Pass command-key combos through WebCore if there is a key binding available for |
| // this event. This lets web pages have a crack at intercepting command-modified keypresses. |
| // But don't do it if we have already handled the event. |
| // Pressing Esc results in a fake event being sent - don't pass it to WebCore. |
| if (!eventWasSentToWebCore && event == [NSApp currentEvent] && self == [[self window] firstResponder]) |
| if (auto* frame = core([self _frame])) |
| ret = frame->eventHandler().keyEvent(event); |
| |
| if (ret) { |
| #if PLATFORM(MAC) |
| [NSCursor setHiddenUntilMouseMoves:YES]; |
| #endif |
| } else |
| ret = [self _handleStyleKeyEquivalent:event] || [super performKeyEquivalent:event]; |
| |
| [self release]; |
| |
| return ret; |
| } |
| |
| - (void)copyFont:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| // Put RTF with font attributes on the pasteboard. |
| // Maybe later we should add a pasteboard type that contains CSS text for "native" copy and paste font. |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| NSPasteboard *fontPasteboard = [NSPasteboard pasteboardWithName:NSFontPboard]; |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| [fontPasteboard declareTypes:@[WebCore::legacyFontPasteboardType()] owner:nil]; |
| [fontPasteboard setData:[self _selectionStartFontAttributesAsRTF] forType:WebCore::legacyFontPasteboardType()]; |
| } |
| |
| - (void)pasteFont:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| // Read RTF with font attributes from the pasteboard. |
| // Maybe later we should add a pasteboard type that contains CSS text for "native" copy and paste font. |
| [self _applyStyleToSelection:[self _styleFromFontAttributes:[self _fontAttributesFromFontPasteboard]] withUndoAction:WebCore::EditAction::PasteFont]; |
| } |
| |
| - (void)pasteAsRichText:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| // Since rich text always beats plain text when both are on the pasteboard, it's not |
| // clear how this is different from plain old paste. |
| [self _pasteWithPasteboard:[NSPasteboard generalPasteboard] allowPlainText:NO]; |
| } |
| |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN |
| - (void)changeFont:(id)sender |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_END |
| { |
| COMMAND_PROLOGUE |
| |
| [self _applyEditingStyleToSelection:WebCore::computedFontChanges(NSFontManager.sharedFontManager).createEditingStyle() withUndoAction:WebCore::EditAction::SetFont]; |
| } |
| |
| - (void)changeAttributes:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| [self _applyEditingStyleToSelection:WebCore::computedFontAttributeChanges(NSFontManager.sharedFontManager, sender).createEditingStyle() withUndoAction:WebCore::EditAction::ChangeAttributes]; |
| } |
| |
| - (DOMCSSStyleDeclaration *)_styleFromColorPanelWithSelector:(SEL)selector |
| { |
| DOMCSSStyleDeclaration *style = [self _emptyStyle]; |
| |
| ASSERT([style respondsToSelector:selector]); |
| [style performSelector:selector withObject:[self _colorAsString:[[NSColorPanel sharedColorPanel] color]]]; |
| |
| return style; |
| } |
| |
| - (WebCore::EditAction)_undoActionFromColorPanelWithSelector:(SEL)selector |
| { |
| if (selector == @selector(setBackgroundColor:)) |
| return WebCore::EditAction::SetBackgroundColor; |
| return WebCore::EditAction::SetColor; |
| } |
| |
| - (void)_changeCSSColorUsingSelector:(SEL)selector inRange:(DOMRange *)range |
| { |
| DOMCSSStyleDeclaration *style = [self _styleFromColorPanelWithSelector:selector]; |
| WebView *webView = [self _webView]; |
| if ([[webView _editingDelegateForwarder] webView:webView shouldApplyStyle:style toElementsInDOMRange:range]) { |
| if (auto* coreFrame = core([self _frame])) { |
| // FIXME: We shouldn't have to make a copy here. |
| Ref<WebCore::MutableStyleProperties> properties(core(style)->copyProperties()); |
| coreFrame->editor().applyStyle(properties.ptr(), [self _undoActionFromColorPanelWithSelector:selector]); |
| } |
| } |
| |
| } |
| |
| - (void)changeDocumentBackgroundColor:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| // Mimicking NSTextView, this method sets the background color for the |
| // entire document. There is no NSTextView API for setting the background |
| // color on the selected range only. Note that this method is currently |
| // never called from the UI (see comment in changeColor:). |
| // FIXME: this actually has no effect when called, probably due to 3654850. _documentRange seems |
| // to do the right thing because it works in startSpeaking:, and I know setBackgroundColor: does the |
| // right thing because I tested it with [self _selectedRange]. |
| // FIXME: This won't actually apply the style to the entire range here, because it ends up calling |
| // [frame _applyStyle:], which operates on the current selection. To make this work right, we'll |
| // need to save off the selection, temporarily set it to the entire range, make the change, then |
| // restore the old selection. |
| [self _changeCSSColorUsingSelector:@selector(setBackgroundColor:) inRange:[self _documentRange]]; |
| } |
| |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN |
| - (void)changeColor:(id)sender |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_END |
| { |
| COMMAND_PROLOGUE |
| |
| // FIXME: in NSTextView, this method calls changeDocumentBackgroundColor: when a |
| // private call has earlier been made by [NSFontFontEffectsBox changeColor:], see 3674493. |
| // AppKit will have to be revised to allow this to work with anything that isn't an |
| // NSTextView. However, this might not be required for Tiger, since the background-color |
| // changing box in the font panel doesn't work in Mail (3674481), though it does in TextEdit. |
| [self _applyStyleToSelection:[self _styleFromColorPanelWithSelector:@selector(setColor:)] withUndoAction:WebCore::EditAction::SetColor]; |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| - (void)_changeWordCaseWithSelector:(SEL)selector |
| { |
| if (![self _canEdit]) |
| return; |
| |
| WebFrame *frame = [self _frame]; |
| [self selectWord:nil]; |
| NSString *word = [[frame _selectedString] performSelector:selector]; |
| // FIXME: Does this need a different action context other than "typed"? |
| if ([self _shouldReplaceSelectionWithText:word givenAction:WebViewInsertActionTyped]) |
| [frame _replaceSelectionWithText:word selectReplacement:NO smartReplace:NO]; |
| } |
| |
| - (void)uppercaseWord:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| [self _changeWordCaseWithSelector:@selector(uppercaseString)]; |
| } |
| |
| - (void)lowercaseWord:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| [self _changeWordCaseWithSelector:@selector(lowercaseString)]; |
| } |
| |
| - (void)capitalizeWord:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| [self _changeWordCaseWithSelector:@selector(capitalizedString)]; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (void)complete:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| if (![self _canEdit]) |
| return; |
| if (!_private->completionController) |
| _private->completionController = adoptNS([[WebTextCompletionController alloc] initWithWebView:[self _webView] HTMLView:self]); |
| [_private->completionController doCompletion]; |
| } |
| |
| - (void)checkSpelling:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| if (auto* coreFrame = core([self _frame])) |
| coreFrame->editor().advanceToNextMisspelling(); |
| } |
| |
| - (void)showGuessPanel:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker]; |
| if (!checker) { |
| LOG_ERROR("No NSSpellChecker"); |
| return; |
| } |
| |
| NSPanel *spellingPanel = [checker spellingPanel]; |
| if ([spellingPanel isVisible]) { |
| [spellingPanel orderOut:sender]; |
| return; |
| } |
| |
| if (auto* coreFrame = core([self _frame])) |
| coreFrame->editor().advanceToNextMisspelling(true); |
| [spellingPanel orderFront:sender]; |
| } |
| |
| - (void)changeSpelling:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| [self _changeSpellingToWord:[[sender selectedCell] stringValue]]; |
| } |
| |
| - (void)performFindPanelAction:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| // Implementing this will probably require copying all of NSFindPanel.h and .m. |
| // We need *almost* the same thing as AppKit, but not quite. |
| LOG_ERROR("unimplemented"); |
| } |
| |
| - (void)startSpeaking:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| WebFrame *frame = [self _frame]; |
| DOMRange *range = [self _selectedRange]; |
| if (!range || [range collapsed]) |
| range = [self _documentRange]; |
| [NSApp speakString:[frame _stringForRange:range]]; |
| } |
| |
| - (void)stopSpeaking:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| [NSApp stopSpeaking:sender]; |
| } |
| |
| - (void)toggleBaseWritingDirection:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| if (![self _canEdit]) |
| return; |
| |
| auto* coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return; |
| |
| auto direction = WebCore::WritingDirection::RightToLeft; |
| switch (coreFrame->editor().baseWritingDirectionForSelectionStart()) { |
| case WebCore::WritingDirection::LeftToRight: |
| break; |
| case WebCore::WritingDirection::RightToLeft: |
| direction = WebCore::WritingDirection::LeftToRight; |
| break; |
| // The writingDirectionForSelectionStart method will never return "natural". It |
| // will always return a concrete direction. So, keep the compiler happy, and assert not reached. |
| case WebCore::WritingDirection::Natural: |
| ASSERT_NOT_REACHED(); |
| break; |
| } |
| |
| if (auto* coreFrame = core([self _frame])) |
| coreFrame->editor().setBaseWritingDirection(direction); |
| } |
| |
| - (void)changeBaseWritingDirection:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| if (![self _canEdit]) |
| return; |
| |
| NSWritingDirection writingDirection = static_cast<NSWritingDirection>([sender tag]); |
| |
| // We disable the menu item that performs this action because we can't implement |
| // NSWritingDirectionNatural's behavior using CSS. |
| ASSERT(writingDirection != NSWritingDirectionNatural); |
| |
| if (auto* coreFrame = core([self _frame])) |
| coreFrame->editor().setBaseWritingDirection(writingDirection == NSWritingDirectionLeftToRight ? WebCore::WritingDirection::LeftToRight : WebCore::WritingDirection::RightToLeft); |
| } |
| |
| static BOOL writingDirectionKeyBindingsEnabled() |
| { |
| return YES; |
| } |
| |
| - (void)_changeBaseWritingDirectionTo:(NSWritingDirection)direction |
| { |
| if (![self _canEdit]) |
| return; |
| |
| static BOOL bindingsEnabled = writingDirectionKeyBindingsEnabled(); |
| |
| if (!bindingsEnabled) { |
| NSBeep(); |
| return; |
| } |
| |
| if (auto* coreFrame = core([self _frame])) |
| coreFrame->editor().setBaseWritingDirection(direction == NSWritingDirectionLeftToRight ? WebCore::WritingDirection::LeftToRight : WebCore::WritingDirection::RightToLeft); |
| } |
| |
| - (void)makeBaseWritingDirectionLeftToRight:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| [self _changeBaseWritingDirectionTo:NSWritingDirectionLeftToRight]; |
| } |
| |
| - (void)makeBaseWritingDirectionRightToLeft:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| [self _changeBaseWritingDirectionTo:NSWritingDirectionRightToLeft]; |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| - (void)makeBaseWritingDirectionNatural:(id)sender |
| { |
| LOG_ERROR("Sent from %@.", sender); |
| } |
| |
| #if 0 |
| |
| // CSS does not have a way to specify an outline font, which may make this difficult to implement. |
| // Maybe a special case of text-shadow? |
| - (void)outline:(id)sender; |
| |
| // This is part of table support, which may be in NSTextView for Tiger. |
| // It's probably simple to do the equivalent thing for WebKit. |
| - (void)insertTable:(id)sender; |
| |
| // This could be important. |
| - (void)toggleTraditionalCharacterShape:(id)sender; |
| |
| // I'm not sure what the equivalents of these in the web world are. |
| - (void)insertLineSeparator:(id)sender; |
| - (void)insertPageBreak:(id)sender; |
| |
| // These methods are not implemented in NSTextView yet at the time of this writing. |
| - (void)changeCaseOfLetter:(id)sender; |
| - (void)transposeWords:(id)sender; |
| |
| #endif |
| |
| // 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 |
| { |
| bool haveWebCoreFrame = core([self _frame]); |
| |
| // If we have a frame, our keyDown method will handle key bindings after sending |
| // the event through the DOM, so ask AppKit not to do its early special key binding |
| // mapping. If we don't have a frame, just let things work the normal way without |
| // a keyDown. |
| return haveWebCoreFrame; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (BOOL)_automaticFocusRingDisabled |
| { |
| // The default state for _automaticFocusRingDisabled is NO, which prevents focus rings |
| // from being painted for search fields. Calling NSSetFocusRingStyle has the side effect |
| // of changing this to YES, so just return YES all the time. <rdar://problem/13780122>, |
| return YES; |
| } |
| |
| - (void)_updateControlTints |
| { |
| auto* frame = core([self _frame]); |
| if (!frame) |
| return; |
| auto* view = frame->view(); |
| if (!view) |
| return; |
| view->updateControlTints(); |
| } |
| |
| // Despite its name, this is called at different times than windowDidBecomeKey is. |
| // It takes into account all the other factors that determine when NSCell draws |
| // with different tints, so it's the right call to use for control tints. We'd prefer |
| // to do this with API. <rdar://problem/5136760> |
| - (void)_windowChangedKeyState |
| { |
| if (pthread_main_np()) |
| [self _updateControlTints]; |
| else |
| [self performSelectorOnMainThread:@selector(_updateControlTints) withObject:nil waitUntilDone:NO]; |
| |
| [super _windowChangedKeyState]; |
| } |
| |
| - (void)otherMouseDown:(NSEvent *)event |
| { |
| if (event.buttonNumber != 2 || [NSMenu menuTypeForEvent:event] == NSMenuTypeContextMenu) { |
| [super otherMouseDown:event]; |
| return; |
| } |
| |
| [self mouseDown:event]; |
| } |
| |
| - (void)otherMouseDragged:(NSEvent *)event |
| { |
| if ([event buttonNumber] == 2) |
| [self mouseDragged:event]; |
| else |
| [super otherMouseDragged:event]; |
| } |
| |
| - (void)otherMouseUp:(NSEvent *)event |
| { |
| if ([event buttonNumber] == 2) |
| [self mouseUp:event]; |
| else |
| [super otherMouseUp:event]; |
| } |
| |
| #endif |
| |
| #if PLATFORM(IOS_FAMILY) |
| - (void)markedTextUpdate:(NSNotification *)notification |
| { |
| NSString *text = [notification object]; |
| NSRange range = NSMakeRange(0, [text length]); |
| [self setMarkedText:text selectedRange:range]; |
| } |
| #endif |
| |
| @end |
| |
| @implementation WebHTMLView (WebInternal) |
| |
| - (void)_selectionChanged |
| { |
| #if PLATFORM(MAC) |
| [self _updateSelectionForInputManager]; |
| [self _updateFontPanel]; |
| if (auto* coreFrame = core([self _frame])) { |
| if (!coreFrame->editor().isHandlingAcceptedCandidate()) |
| _private->softSpaceRange = NSMakeRange(NSNotFound, 0); |
| } |
| #endif |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (void)_updateFontPanel |
| { |
| // FIXME: NSTextView bails out if becoming or resigning first responder, for which it has ivar flags. Not |
| // sure if we need to do something similar. |
| |
| if (![self _canEdit]) |
| return; |
| |
| NSWindow *window = [self window]; |
| // FIXME: is this first-responder check correct? What happens if a subframe is editable and is first responder? |
| if (![window isKeyWindow] || [window firstResponder] != self) |
| return; |
| |
| bool multipleFonts = false; |
| NSFont *font = nil; |
| RetainPtr<NSDictionary> attributes; |
| if (auto* coreFrame = core([self _frame])) { |
| if (auto coreFont = coreFrame->editor().fontForSelection(multipleFonts)) |
| font = (NSFont *)coreFont->platformData().registeredFont(); |
| attributes = coreFrame->editor().fontAttributesAtSelectionStart().createDictionary(); |
| } |
| |
| // FIXME: for now, return a bogus font that distinguishes the empty selection from the non-empty |
| // selection. We should be able to remove this once the rest of this code works properly. |
| if (font == nil) |
| font = [self _hasSelection] ? [NSFont menuFontOfSize:23] : [NSFont toolTipsFontOfSize:17]; |
| ASSERT(font != nil); |
| |
| NSFontManager *fontManager = [NSFontManager sharedFontManager]; |
| [fontManager setSelectedFont:font isMultiple:multipleFonts]; |
| [fontManager setSelectedAttributes:(attributes ? attributes.get() : @{ }) isMultiple:multipleFonts]; |
| } |
| |
| - (void)_setSoftSpaceRange:(NSRange)range |
| { |
| _private->softSpaceRange = range; |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| - (BOOL)_canSmartCopyOrDelete |
| { |
| if (![[self _webView] smartInsertDeleteEnabled]) |
| return NO; |
| auto* coreFrame = core([self _frame]); |
| return coreFrame && coreFrame->selection().granularity() == WebCore::TextGranularity::WordGranularity; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (NSEvent *)_mouseDownEvent |
| { |
| return _private->mouseDownEvent.get(); |
| } |
| |
| #endif |
| |
| - (WebFrame *)_frame |
| { |
| return [_private->dataSource webFrame]; |
| } |
| |
| - (void)closeIfNotCurrentView |
| { |
| if ([[[self _frame] frameView] documentView] != self) |
| [self close]; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (DOMDocumentFragment*)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard |
| { |
| return [self _documentFragmentFromPasteboard:pasteboard inContext:nil allowPlainText:NO]; |
| } |
| |
| |
| - (BOOL)isGrammarCheckingEnabled |
| { |
| // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must implement the method here because |
| // the AppKit code checks the first responder. |
| return [[self _webView] isGrammarCheckingEnabled]; |
| } |
| |
| - (void)setGrammarCheckingEnabled:(BOOL)flag |
| { |
| // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must implement the method here because |
| // the AppKit code checks the first responder. |
| [[self _webView] setGrammarCheckingEnabled:flag]; |
| } |
| |
| - (void)toggleGrammarChecking:(id)sender |
| { |
| // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must implement the method here because |
| // the AppKit code checks the first responder. |
| [[self _webView] toggleGrammarChecking:sender]; |
| } |
| |
| - (void)orderFrontSubstitutionsPanel:(id)sender |
| { |
| COMMAND_PROLOGUE |
| |
| 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]; |
| } |
| |
| // FIXME 4799134: WebView is the bottleneck for this logic, but we must implement these methods here because |
| // the AppKit code checks the first responder. |
| |
| - (BOOL)smartInsertDeleteEnabled |
| { |
| return [[self _webView] smartInsertDeleteEnabled]; |
| } |
| |
| - (void)setSmartInsertDeleteEnabled:(BOOL)flag |
| { |
| [[self _webView] setSmartInsertDeleteEnabled:flag]; |
| } |
| |
| - (void)toggleSmartInsertDelete:(id)sender |
| { |
| [[self _webView] toggleSmartInsertDelete:sender]; |
| } |
| |
| - (BOOL)isAutomaticQuoteSubstitutionEnabled |
| { |
| return [[self _webView] isAutomaticQuoteSubstitutionEnabled]; |
| } |
| |
| - (void)setAutomaticQuoteSubstitutionEnabled:(BOOL)flag |
| { |
| [[self _webView] setAutomaticQuoteSubstitutionEnabled:flag]; |
| } |
| |
| - (void)toggleAutomaticQuoteSubstitution:(id)sender |
| { |
| [[self _webView] toggleAutomaticQuoteSubstitution:sender]; |
| } |
| |
| - (BOOL)isAutomaticLinkDetectionEnabled |
| { |
| return [[self _webView] isAutomaticLinkDetectionEnabled]; |
| } |
| |
| - (void)setAutomaticLinkDetectionEnabled:(BOOL)flag |
| { |
| [[self _webView] setAutomaticLinkDetectionEnabled:flag]; |
| } |
| |
| - (void)toggleAutomaticLinkDetection:(id)sender |
| { |
| [[self _webView] toggleAutomaticLinkDetection:sender]; |
| } |
| |
| - (BOOL)isAutomaticDashSubstitutionEnabled |
| { |
| return [[self _webView] isAutomaticDashSubstitutionEnabled]; |
| } |
| |
| - (void)setAutomaticDashSubstitutionEnabled:(BOOL)flag |
| { |
| [[self _webView] setAutomaticDashSubstitutionEnabled:flag]; |
| } |
| |
| - (void)toggleAutomaticDashSubstitution:(id)sender |
| { |
| [[self _webView] toggleAutomaticDashSubstitution:sender]; |
| } |
| |
| - (BOOL)isAutomaticTextReplacementEnabled |
| { |
| return [[self _webView] isAutomaticTextReplacementEnabled]; |
| } |
| |
| - (void)setAutomaticTextReplacementEnabled:(BOOL)flag |
| { |
| [[self _webView] setAutomaticTextReplacementEnabled:flag]; |
| } |
| |
| - (void)toggleAutomaticTextReplacement:(id)sender |
| { |
| [[self _webView] toggleAutomaticTextReplacement:sender]; |
| } |
| |
| - (BOOL)isAutomaticSpellingCorrectionEnabled |
| { |
| return [[self _webView] isAutomaticSpellingCorrectionEnabled]; |
| } |
| |
| - (void)setAutomaticSpellingCorrectionEnabled:(BOOL)flag |
| { |
| [[self _webView] setAutomaticSpellingCorrectionEnabled:flag]; |
| } |
| |
| - (void)toggleAutomaticSpellingCorrection:(id)sender |
| { |
| [[self _webView] toggleAutomaticSpellingCorrection:sender]; |
| } |
| |
| - (void)_lookUpInDictionaryFromMenu:(id)sender |
| { |
| auto* coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return; |
| |
| auto selectionRange = coreFrame->selection().selection().firstRange(); |
| if (!selectionRange) |
| return; |
| |
| [[self _webView] _showDictionaryLookupPopup:[WebImmediateActionController _dictionaryPopupInfoForRange:*selectionRange inFrame:coreFrame withLookupOptions:nil indicatorOptions:{ WebCore::TextIndicatorOption::IncludeSnapshotWithSelectionHighlight } transition:WebCore::TextIndicatorPresentationTransition::BounceAndCrossfade]]; |
| } |
| |
| - (void)quickLookWithEvent:(NSEvent *)event |
| { |
| [[self _webView] _clearTextIndicatorWithAnimation:WebCore::TextIndicatorDismissalAnimation::FadeOut]; |
| [super quickLookWithEvent:event]; |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| #undef COMMAND_PROLOGUE |
| |
| - (void)_executeSavedKeypressCommands |
| { |
| auto* parameters = _private->interpretKeyEventsParameters; |
| if (!parameters || parameters->event->keypressCommands().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; |
| |
| // Avoid an infinite loop that would occur if executing a command appended it to event->keypressCommands() again. |
| bool wasSavingCommands = parameters->shouldSaveCommands; |
| parameters->shouldSaveCommands = false; |
| parameters->executingSavedKeypressCommands = true; |
| |
| const Vector<WebCore::KeypressCommand>& commands = parameters->event->keypressCommands(); |
| |
| for (size_t i = 0; i < commands.size(); ++i) { |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| if (commands[i].commandName == "insertText:"_s) |
| [self insertText:commands[i].text]; |
| else if (commands[i].commandName == "noop:"_s) |
| ; // Do nothing. This case can be removed once <rdar://problem/9025012> is fixed. |
| else |
| [self doCommandBySelector:NSSelectorFromString(commands[i].commandName)]; |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| } |
| parameters->event->keypressCommands().clear(); |
| parameters->shouldSaveCommands = wasSavingCommands; |
| parameters->executingSavedKeypressCommands = false; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (BOOL)_interpretKeyEvent:(NakedPtr<WebCore::KeyboardEvent>)event savingCommands:(BOOL)savingCommands |
| { |
| ASSERT(core([self _frame]) == downcast<WebCore::Node>(event->target())->document().frame()); |
| ASSERT(!savingCommands || event->keypressCommands().isEmpty()); // Save commands once for each event. |
| |
| WebHTMLViewInterpretKeyEventsParameters parameters; |
| parameters.eventInterpretationHadSideEffects = false; |
| parameters.shouldSaveCommands = savingCommands; |
| parameters.executingSavedKeypressCommands = false; |
| // If we're intercepting the initial IM call we assume that the IM has consumed the event, |
| // and only change this assumption if one of the NSTextInput/Responder callbacks is used. |
| // We assume the IM will *not* consume hotkey sequences |
| parameters.consumedByIM = savingCommands && !event->metaKey(); |
| |
| auto* platformEvent = event->underlyingPlatformEvent(); |
| if (!platformEvent) |
| return NO; |
| |
| NSEvent *macEvent = platformEvent->macEvent(); |
| if ([macEvent type] == NSEventTypeKeyDown && [_private->completionController filterKeyDown:macEvent]) |
| return YES; |
| |
| if ([macEvent type] == NSEventTypeFlagsChanged) |
| return NO; |
| |
| parameters.event = event; |
| _private->interpretKeyEventsParameters = ¶meters; |
| const Vector<WebCore::KeypressCommand>& commands = event->keypressCommands(); |
| |
| if (savingCommands) { |
| // AppKit will respond with a series of NSTextInput protocol method calls. There are three groups that we heuristically differentiate: |
| // 1. Key Bindings. Only doCommandBySelector: and insertText: calls will be made, which we save in the event for execution |
| // after DOM dispatch. This is safe, because neither returns a result, so there is no branching on AppKit side. |
| // 2. Plain text input. Here as well, we need to dispatch DOM events prior to inserting text, so we save the insertText: command. |
| // 3. Input method processing. An IM can make any NSTextInput calls, and can base its decisions on results it gets, so we must |
| // execute the calls immediately. DOM events like keydown are tweaked to have keyCode of 229, and canceling them has no effect. |
| // Unfortunately, there is no real difference between plain text input and IM processing - for example, AppKit queries hasMarkedText |
| // when typing with U.S. keyboard, and inserts marked text for dead keys. |
| [self interpretKeyEvents:@[macEvent]]; |
| } else { |
| // Are there commands that could just cause text insertion if executed via Editor? |
| // WebKit doesn't have enough information about mode to decide how they should be treated, so we leave it upon WebCore |
| // to either handle them immediately (e.g. Tab that changes focus) or let a keypress event be generated |
| // (e.g. Tab that inserts a Tab character, or Enter). |
| bool haveTextInsertionCommands = false; |
| for (size_t i = 0; i < commands.size(); ++i) { |
| if ([self coreCommandBySelector:NSSelectorFromString(commands[i].commandName)].isTextInsertion()) |
| haveTextInsertionCommands = true; |
| } |
| // If there are no text insertion commands, default keydown handler is the right time to execute the commands. |
| // Keypress (Char event) handler is the latest opportunity to execute. |
| if (!haveTextInsertionCommands || platformEvent->type() == WebCore::PlatformEvent::Char) |
| [self _executeSavedKeypressCommands]; |
| } |
| _private->interpretKeyEventsParameters = nullptr; |
| |
| // 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 parameters.eventInterpretationHadSideEffects is true), but there are saved commands that |
| // should be handled like normal text input after DOM event dispatch. |
| if (!event->keypressCommands().isEmpty()) |
| return NO; |
| |
| // 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) |
| return YES; |
| |
| // If we have already executed all commands, don't do it again. |
| return parameters.eventInterpretationHadSideEffects; |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| #if PLATFORM(IOS_FAMILY) |
| |
| - (BOOL)_handleEditingKeyEvent:(WebCore::KeyboardEvent *)wcEvent |
| { |
| // Use the isEditable state to determine whether or not to process tab key events. |
| // The idea here is that isEditable will be NO when this WebView is being used |
| // in a browser, and we desire the behavior where tab moves to the next element |
| // in tab order. If isEditable is YES, it is likely that the WebView is being |
| // embedded as the whole view, as in Mail, and tabs should input tabs as expected |
| // in a text editor. |
| |
| if (auto* platformEvent = wcEvent->underlyingPlatformEvent()) { |
| WebEvent *event = platformEvent->event(); |
| if (event.keyboardFlags & WebEventKeyboardInputModifierFlagsChanged) |
| return NO; |
| |
| WebView *webView = [self _webView]; |
| if (!webView.isEditable && event.isTabKey) |
| return NO; |
| |
| bool isCharEvent = platformEvent->type() == WebCore::PlatformKeyboardEvent::Char; |
| |
| if (!isCharEvent && [webView._UIKitDelegateForwarder handleKeyTextCommandForCurrentEvent]) |
| return YES; |
| if (isCharEvent && [webView._UIKitDelegateForwarder handleKeyAppCommandForCurrentEvent]) |
| return YES; |
| |
| NSString *s = [event characters]; |
| if (!s.length) |
| return NO; |
| switch ([s characterAtIndex:0]) { |
| case NSBackspaceCharacter: |
| case NSDeleteCharacter: |
| [[webView _UIKitDelegateForwarder] deleteFromInputWithFlags:event.keyboardFlags]; |
| return YES; |
| case NSEnterCharacter: |
| case NSCarriageReturnCharacter: |
| if (isCharEvent) { |
| // Map \r from HW keyboard to \n to match the behavior of the soft keyboard. |
| [[webView _UIKitDelegateForwarder] addInputString:@"\n" withFlags:0]; |
| return YES; |
| } |
| break; |
| default: |
| if (isCharEvent) { |
| [[webView _UIKitDelegateForwarder] addInputString:event.characters withFlags:event.keyboardFlags]; |
| return YES; |
| } |
| } |
| } |
| return NO; |
| } |
| #endif // PLATFORM(IOS_FAMILY) |
| |
| #if PLATFORM(MAC) |
| |
| - (void)setPromisedDragTIFFDataSource:(NakedPtr<WebCore::CachedImage>)source |
| { |
| if (source) |
| source->addClient(promisedDataClient()); |
| |
| if (_private->promisedDragTIFFDataSource) |
| _private->promisedDragTIFFDataSource->removeClient(promisedDataClient()); |
| _private->promisedDragTIFFDataSource = source; |
| } |
| |
| #endif |
| |
| - (void)_layoutIfNeeded |
| { |
| #if PLATFORM(MAC) |
| ASSERT(!_private->subviewsSetAside); |
| #endif |
| |
| if ([self _needsLayout]) |
| [self layout]; |
| } |
| |
| - (void)_web_updateLayoutAndStyleIfNeededRecursive |
| { |
| WebFrame *webFrame = [self _frame]; |
| auto* coreFrame = core(webFrame); |
| if (coreFrame && coreFrame->view()) |
| coreFrame->view()->updateLayoutAndStyleIfNeededRecursive(); |
| } |
| |
| - (void) _destroyAllWebPlugins |
| { |
| [[self _pluginController] destroyAllPlugins]; |
| } |
| |
| - (BOOL)_needsLayout |
| { |
| return [[self _frame] _needsLayout]; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (void)attachRootLayer:(CALayer *)layer |
| { |
| if (!_private->layerHostingView) { |
| auto hostingView = adoptNS([[WebLayerHostingFlippedView alloc] initWithFrame:[self bounds]]); |
| [hostingView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)]; |
| [self addSubview:hostingView.get()]; |
| // hostingView is owned by being a subview of self |
| _private->layerHostingView = hostingView.get(); |
| } |
| |
| // Make a container layer, which will get sized/positioned by AppKit and CA. |
| CALayer* viewLayer = [WebRootLayer layer]; |
| |
| if ([self layer]) { |
| // If we are in a layer-backed view, we need to manually initialize the geometry for our layer. |
| [viewLayer setBounds:NSRectToCGRect([_private->layerHostingView bounds])]; |
| [viewLayer setAnchorPoint:CGPointMake(0, [self isFlipped] ? 1 : 0)]; |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| CGPoint layerPosition = NSPointToCGPoint([self convertPointToBase:[_private->layerHostingView frame].origin]); |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| [viewLayer setPosition:layerPosition]; |
| } |
| |
| [_private->layerHostingView setLayer:viewLayer]; |
| [_private->layerHostingView setWantsLayer:YES]; |
| |
| // Parent our root layer in the container layer |
| [viewLayer addSublayer:layer]; |
| |
| if ([[self _webView] _postsAcceleratedCompositingNotifications]) |
| [[NSNotificationCenter defaultCenter] postNotificationName:_WebViewDidStartAcceleratedCompositingNotification object:[self _webView] userInfo:nil]; |
| |
| if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionMountainLion)) |
| [viewLayer setGeometryFlipped:YES]; |
| } |
| |
| - (void)detachRootLayer |
| { |
| if (_private->layerHostingView) { |
| [_private->layerHostingView setLayer:nil]; |
| [_private->layerHostingView setWantsLayer:NO]; |
| [_private->layerHostingView removeFromSuperview]; |
| _private->layerHostingView = nil; |
| } |
| } |
| |
| - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx |
| { |
| if (_private) { |
| ASSERT(!_private->drawingIntoLayer); |
| _private->drawingIntoLayer = YES; |
| _private->drawingIntoAcceleratedLayer = [layer drawsAsynchronously]; |
| } |
| |
| [super drawLayer:layer inContext:ctx]; |
| |
| if (_private) { |
| _private->drawingIntoLayer = NO; |
| _private->drawingIntoAcceleratedLayer = NO; |
| } |
| } |
| |
| - (BOOL)_web_isDrawingIntoLayer |
| { |
| return _private->drawingIntoLayer; |
| } |
| |
| - (BOOL)_web_isDrawingIntoAcceleratedLayer |
| { |
| return _private->drawingIntoAcceleratedLayer; |
| } |
| |
| - (void)_changeSpellingToWord:(NSString *)newWord |
| { |
| if (![self _canEdit]) |
| return; |
| |
| if (![NSSpellChecker sharedSpellChecker]) { |
| LOG_ERROR("No NSSpellChecker"); |
| return; |
| } |
| |
| // Don't correct to empty string. (AppKit checked this, we might as well too.) |
| if ([newWord isEqualToString:@""]) |
| return; |
| |
| if ([self _shouldReplaceSelectionWithText:newWord givenAction:WebViewInsertActionPasted]) |
| [[self _frame] _replaceSelectionWithText:newWord selectReplacement:YES smartReplace:NO]; |
| } |
| |
| - (void)_startAutoscrollTimer:(NSEvent *)triggerEvent |
| { |
| if (_private->autoscrollTimer) |
| return; |
| _private->autoscrollTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(_autoscroll) userInfo:nil repeats:YES]; |
| _private->autoscrollTriggerEvent = triggerEvent; |
| } |
| |
| #endif |
| |
| - (void)_stopAutoscrollTimer |
| { |
| #if PLATFORM(MAC) |
| [_private->autoscrollTimer invalidate]; |
| _private->autoscrollTimer = nil; |
| _private->autoscrollTriggerEvent = nil; |
| #endif |
| } |
| |
| - (WebPluginController *)_pluginController |
| { |
| return _private->pluginController.get(); |
| } |
| |
| @end |
| |
| @implementation WebHTMLView (WebNSTextInputSupport) |
| |
| #if PLATFORM(MAC) |
| |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN |
| - (NSArray *)validAttributesForMarkedText |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_END |
| { |
| static NSArray *validAttributes = [[NSArray alloc] initWithObjects: |
| NSUnderlineStyleAttributeName, |
| NSUnderlineColorAttributeName, |
| NSMarkedClauseSegmentAttributeName, |
| NSTextInputReplacementRangeAttributeName, |
| NSTextAlternativesAttributeName, |
| NSTextInsertionUndoableAttributeName, |
| nil]; |
| LOG(TextInput, "validAttributesForMarkedText -> (...)"); |
| return validAttributes; |
| } |
| |
| - (NSTextInputContext *)inputContext |
| { |
| return _private->exposeInputContext ? [super inputContext] : nil; |
| } |
| |
| - (NSAttributedString *)textStorage |
| { |
| if (!_private->exposeInputContext) { |
| LOG(TextInput, "textStorage -> nil"); |
| return nil; |
| } |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| NSAttributedString *result = [self attributedSubstringFromRange:NSMakeRange(0, UINT_MAX)]; |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| |
| LOG(TextInput, "textStorage -> \"%@\"", result ? [result string] : @""); |
| |
| // We have to return an empty string rather than null to prevent TSM from calling -string |
| return result ? result : adoptNS([[NSAttributedString alloc] init]).autorelease(); |
| } |
| |
| #endif // PLATFORM(MAC) |
| |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN |
| - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_END |
| { |
| [self _executeSavedKeypressCommands]; |
| |
| NSWindow *window = [self window]; |
| WebFrame *frame = [self _frame]; |
| |
| if (window) { |
| NSRect screenRect = { thePoint, NSZeroSize }; |
| thePoint = [window convertRectFromScreen:screenRect].origin; |
| } |
| thePoint = [self convertPoint:thePoint fromView:nil]; |
| |
| DOMRange *range = [frame _characterRangeAtPoint:thePoint]; |
| if (!range) { |
| LOG(TextInput, "characterIndexForPoint:(%f, %f) -> NSNotFound", thePoint.x, thePoint.y); |
| return NSNotFound; |
| } |
| |
| unsigned result = [frame _convertDOMRangeToNSRange:range].location; |
| LOG(TextInput, "characterIndexForPoint:(%f, %f) -> %u", thePoint.x, thePoint.y, result); |
| return result; |
| } |
| |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN |
| - (NSRect)firstRectForCharacterRange:(NSRange)theRange |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_END |
| { |
| [self _executeSavedKeypressCommands]; |
| |
| WebFrame *frame = [self _frame]; |
| |
| // 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; |
| |
| DOMRange *range = [frame _convertNSRangeToDOMRange:theRange]; |
| if (!range) { |
| LOG(TextInput, "firstRectForCharacterRange:(%u, %u) -> (0, 0, 0, 0)", theRange.location, theRange.length); |
| return NSZeroRect; |
| } |
| |
| ASSERT([range startContainer]); |
| ASSERT([range endContainer]); |
| |
| NSRect resultRect = [frame _firstRectForDOMRange:range]; |
| resultRect = [self convertRect:resultRect toView:nil]; |
| |
| NSWindow *window = [self window]; |
| if (window) |
| resultRect.origin = [window convertRectToScreen:resultRect].origin; |
| |
| 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; |
| } |
| |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN |
| - (NSRange)selectedRange |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_END |
| { |
| [self _executeSavedKeypressCommands]; |
| |
| if (!isTextInput(core([self _frame]))) { |
| LOG(TextInput, "selectedRange -> (NSNotFound, 0)"); |
| return NSMakeRange(NSNotFound, 0); |
| } |
| NSRange result = [[self _frame] _selectedNSRange]; |
| |
| LOG(TextInput, "selectedRange -> (%u, %u)", result.location, result.length); |
| return result; |
| } |
| |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN |
| - (NSRange)markedRange |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_END |
| { |
| [self _executeSavedKeypressCommands]; |
| |
| WebFrame *webFrame = [self _frame]; |
| auto* coreFrame = core(webFrame); |
| if (!coreFrame) |
| return NSMakeRange(0, 0); // FIXME: Why not NSNotFound, 0? |
| |
| auto range = coreFrame->editor().compositionRange(); |
| if (!range) |
| return NSMakeRange(NSNotFound, 0); |
| |
| NSRange result = [webFrame _convertToNSRange:*range]; |
| LOG(TextInput, "markedRange -> (%u, %u)", result.location, result.length); |
| return result; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN |
| - (NSAttributedString *)attributedSubstringFromRange:(NSRange)nsRange |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_END |
| { |
| [self _executeSavedKeypressCommands]; |
| |
| WebFrame *frame = [self _frame]; |
| auto* coreFrame = core(frame); |
| if (!isTextInput(coreFrame) || isInPasswordField(coreFrame)) { |
| LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> nil", nsRange.location, nsRange.length); |
| return nil; |
| } |
| auto range = [frame _convertToDOMRange:nsRange]; |
| if (!range) { |
| LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> nil", nsRange.location, nsRange.length); |
| return nil; |
| } |
| |
| auto result = editingAttributedString(*range).string; |
| |
| // WebCore::editingAttributedStringFromRange() insists on inserting a trailing |
| // whitespace at the end of the string which breaks the ATOK input method. <rdar://problem/5400551> |
| // To work around this we truncate the resultant string to the correct length. |
| if ([result length] > nsRange.length) { |
| ASSERT([result length] == nsRange.length + 1); |
| ASSERT([[result string] characterAtIndex:nsRange.length] == '\n' || [[result string] characterAtIndex:nsRange.length] == ' '); |
| result = [result attributedSubstringFromRange:NSMakeRange(0, nsRange.length)]; |
| } |
| LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> \"%@\"", nsRange.location, nsRange.length, [result string]); |
| return result.autorelease(); |
| } |
| |
| #endif |
| |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN |
| - (NSInteger)conversationIdentifier |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_END |
| { |
| return (NSInteger)self; |
| } |
| |
| - (BOOL)hasMarkedText |
| { |
| auto* coreFrame = core([self _frame]); |
| BOOL result = coreFrame && coreFrame->editor().hasComposition(); |
| |
| if (result) { |
| // A saved command can confirm a composition, but it cannot start a new one. |
| [self _executeSavedKeypressCommands]; |
| result = coreFrame->editor().hasComposition(); |
| } |
| |
| LOG(TextInput, "hasMarkedText -> %u", result); |
| return result; |
| } |
| |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN |
| - (void)unmarkText |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_END |
| { |
| [self _executeSavedKeypressCommands]; |
| |
| LOG(TextInput, "unmarkText"); |
| |
| // Use pointer to get parameters passed to us by the caller of interpretKeyEvents. |
| auto* parameters = _private->interpretKeyEventsParameters; |
| |
| if (parameters) { |
| parameters->eventInterpretationHadSideEffects = true; |
| parameters->consumedByIM = false; |
| } |
| |
| if (auto* coreFrame = core([self _frame])) |
| coreFrame->editor().confirmComposition(); |
| } |
| |
| #if PLATFORM(MAC) |
| |
| static void extractUnderlines(NSAttributedString *string, Vector<WebCore::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]) { |
| WebCore::Color color = WebCore::Color::black; |
| auto compositionUnderlineColor = WebCore::CompositionUnderlineColor::TextColor; |
| if (NSColor *colorAttr = [attrs objectForKey:NSUnderlineColorAttributeName]) { |
| color = WebCore::colorFromCocoaColor(colorAttr); |
| compositionUnderlineColor = WebCore::CompositionUnderlineColor::GivenColor; |
| } |
| result.append(WebCore::CompositionUnderline(range.location, NSMaxRange(range), compositionUnderlineColor, color, [style intValue] > 1)); |
| } |
| |
| i = range.location + range.length; |
| } |
| } |
| |
| #endif |
| |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN |
| - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_END |
| { |
| [self _executeSavedKeypressCommands]; |
| |
| #if PLATFORM(MAC) |
| BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; |
| ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]); |
| |
| LOG(TextInput, "setMarkedText:\"%@\" selectedRange:(%u, %u)", isAttributedString ? [string string] : string, newSelRange.location, newSelRange.length); |
| #endif |
| |
| // Use pointer to get parameters passed to us by the caller of interpretKeyEvents. |
| auto* parameters = _private->interpretKeyEventsParameters; |
| |
| if (parameters) { |
| parameters->eventInterpretationHadSideEffects = true; |
| parameters->consumedByIM = false; |
| } |
| |
| auto* coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return; |
| |
| if (![self _isEditable]) |
| return; |
| |
| Vector<WebCore::CompositionUnderline> underlines; |
| NSString *text; |
| NSRange replacementRange = { NSNotFound, 0 }; |
| |
| #if PLATFORM(MAC) |
| 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]; |
| NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:0 inRange:NSMakeRange(0, [text length])]; |
| LOG(TextInput, " ReplacementRange: %@", rangeString); |
| // The AppKit adds a 'secret' property to the string that contains the replacement range. |
| // The replacement range is the range of the text that should be replaced with the new string. |
| if (rangeString) |
| replacementRange = NSRangeFromString(rangeString); |
| |
| extractUnderlines(string, underlines); |
| } else { |
| text = string; |
| underlines.append(WebCore::CompositionUnderline(0, [text length], WebCore::CompositionUnderlineColor::TextColor, WebCore::Color::black, false)); |
| } |
| #else |
| text = string; |
| #endif |
| |
| if (replacementRange.location != NSNotFound) |
| [[self _frame] _selectNSRange:replacementRange]; |
| |
| coreFrame->editor().setComposition(text, underlines, { }, newSelRange.location, NSMaxRange(newSelRange)); |
| } |
| |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN |
| - (void)doCommandBySelector:(SEL)selector |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_END |
| { |
| LOG(TextInput, "doCommandBySelector:\"%s\"", sel_getName(selector)); |
| |
| // Use pointer to get parameters passed to us by the caller of interpretKeyEvents. |
| // The same call to interpretKeyEvents can do more than one command. |
| auto* parameters = _private->interpretKeyEventsParameters; |
| if (parameters) |
| parameters->consumedByIM = false; |
| |
| auto* event = parameters ? parameters->event : 0; |
| bool shouldSaveCommand = parameters && parameters->shouldSaveCommands; |
| |
| // As in insertText:, we assume that the call comes from an input method if there is marked text. |
| RefPtr<WebCore::Frame> coreFrame = core([self _frame]); |
| bool isFromInputMethod = coreFrame && coreFrame->editor().hasComposition(); |
| |
| if (event && shouldSaveCommand && !isFromInputMethod) |
| event->keypressCommands().append(WebCore::KeypressCommand(NSStringFromSelector(selector))); |
| else { |
| // Make sure that only direct calls to doCommandBySelector: see the parameters by setting to 0. |
| _private->interpretKeyEventsParameters = 0; |
| |
| bool eventWasHandled; |
| |
| WebView *webView = [self _webView]; |
| if ([[webView _editingDelegateForwarder] webView:webView doCommandBySelector:selector]) |
| eventWasHandled = true; |
| else { |
| WebCore::Editor::Command command = [self coreCommandBySelector:selector]; |
| if (command.isSupported()) |
| eventWasHandled = command.execute(event); |
| else { |
| #if PLATFORM(MAC) |
| // If WebKit does not support this command, we need to pass the selector to super. |
| _private->selectorForDoCommandBySelector = 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. |
| auto sink = adoptNS([[WebResponderChainSink alloc] initWithResponderChain:self]); |
| [super doCommandBySelector:selector]; |
| eventWasHandled = ![sink receivedUnhandledCommand]; |
| [sink detach]; |
| |
| _private->selectorForDoCommandBySelector = 0; |
| #else |
| eventWasHandled = false; |
| #endif |
| } |
| } |
| |
| if (parameters) |
| parameters->eventInterpretationHadSideEffects |= eventWasHandled; |
| |
| _private->interpretKeyEventsParameters = parameters; |
| } |
| } |
| |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_BEGIN |
| - (void)insertText:(id)string |
| ALLOW_DEPRECATED_IMPLEMENTATIONS_END |
| { |
| #if PLATFORM(MAC) |
| BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; |
| ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]); |
| |
| LOG(TextInput, "insertText:\"%@\"", isAttributedString ? [string string] : string); |
| #endif |
| |
| auto* parameters = _private->interpretKeyEventsParameters; |
| if (parameters) |
| parameters->consumedByIM = false; |
| |
| RefPtr<WebCore::Frame> coreFrame = core([self _frame]); |
| NSString *text; |
| NSRange replacementRange = { NSNotFound, 0 }; |
| bool isFromInputMethod = coreFrame && coreFrame->editor().hasComposition(); |
| #if USE(INSERTION_UNDO_GROUPING) |
| bool registerUndoGroup = false; |
| #endif |
| |
| Vector<WebCore::DictationAlternative> dictationAlternativeLocations; |
| #if PLATFORM(MAC) |
| if (isAttributedString) { |
| Vector<WebCore::TextAlternativeWithRange> textAlternatives; |
| collectDictationTextAlternatives(string, textAlternatives); |
| if (!textAlternatives.isEmpty()) |
| [[self _webView] _getWebCoreDictationAlternatives:dictationAlternativeLocations fromTextAlternatives:textAlternatives]; |
| registerUndoGroup = WebCore::shouldRegisterInsertionUndoGroup(string); |
| // FIXME: We ignore most attributes from the string, so for example inserting from Character Palette loses font and glyph variation data. |
| // It does not look like any input methods ever use insertText: with attributes other than NSTextInputReplacementRangeAttributeName. |
| text = [string string]; |
| NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:0 inRange:NSMakeRange(0, [text length])]; |
| LOG(TextInput, " ReplacementRange: %@", rangeString); |
| if (rangeString) { |
| replacementRange = NSRangeFromString(rangeString); |
| isFromInputMethod = true; |
| } |
| } else |
| #endif |
| text = string; |
| |
| auto* event = parameters ? parameters->event : 0; |
| |
| // 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. |
| bool shouldSaveCommand = parameters && parameters->shouldSaveCommands; |
| if (event && shouldSaveCommand && !isFromInputMethod) { |
| auto isFunctionKeyCommandWithMatchingMenuItem = ([&] { |
| #if PLATFORM(MAC) |
| auto menu = NSApp.mainMenu; |
| auto* platformKeyEvent = event->underlyingPlatformEvent(); |
| if (!platformKeyEvent) |
| return NO; |
| |
| NSEvent *nsEvent = platformKeyEvent->macEvent(); |
| if (!(nsEvent.modifierFlags & NSEventModifierFlagFunction)) |
| return NO; |
| |
| if (![menu respondsToSelector:@selector(_containsItemMatchingEvent:includingDisabledItems:)]) |
| return NO; |
| |
| return [menu _containsItemMatchingEvent:nsEvent includingDisabledItems:YES]; |
| #else |
| return NO; |
| #endif |
| })(); |
| |
| if (!isFunctionKeyCommandWithMatchingMenuItem) |
| event->keypressCommands().append(WebCore::KeypressCommand("insertText:"_s, text)); |
| return; |
| } |
| |
| if (!coreFrame || !coreFrame->editor().canEdit()) |
| return; |
| |
| BOOL needToRemoveSoftSpace = NO; |
| #if PLATFORM(MAC) |
| if (_private->softSpaceRange.location != NSNotFound && (replacementRange.location == NSMaxRange(_private->softSpaceRange) || replacementRange.location == NSNotFound) && !replacementRange.length && [[NSSpellChecker sharedSpellChecker] deletesAutospaceBeforeString:text language:nil]) { |
| replacementRange = _private->softSpaceRange; |
| needToRemoveSoftSpace = YES; |
| } |
| _private->softSpaceRange = NSMakeRange(NSNotFound, 0); |
| #endif |
| |
| bool replacesText = false; |
| if (replacementRange.location != NSNotFound) { |
| WebRangeIsRelativeTo rangeIsRelativeTo = needToRemoveSoftSpace ? WebRangeIsRelativeTo::Paragraph : WebRangeIsRelativeTo::EditableRoot; |
| if (auto domRange = [[self _frame] _convertToDOMRange:replacementRange rangeIsRelativeTo:rangeIsRelativeTo]) { |
| WebCore::IgnoreSelectionChangeForScope selectionChange { *coreFrame }; |
| coreFrame->selection().setSelection(WebCore::VisibleSelection(*domRange)); |
| replacesText = replacementRange.length; |
| } |
| } |
| |
| bool eventHandled = false; |
| String eventText = makeStringByReplacingAll(text, NSBackTabCharacter, NSTabCharacter); // same thing is done in KeyEventMac.mm in WebCore |
| if (!coreFrame->editor().hasComposition()) { |
| // An insertText: might be handled by other responders in the chain if we don't handle it. |
| // One example is space bar that results in scrolling down the page. |
| |
| if (!dictationAlternativeLocations.isEmpty()) |
| eventHandled = coreFrame->editor().insertDictatedText(eventText, dictationAlternativeLocations, event); |
| else |
| eventHandled = coreFrame->editor().insertText(eventText, event, replacesText ? WebCore::TextEventInputAutocompletion : WebCore::TextEventInputKeyboard); |
| |
| #if USE(INSERTION_UNDO_GROUPING) |
| if (registerUndoGroup) |
| WebCore::registerInsertionUndoGroupingWithUndoManager([[self _webView] undoManager]); |
| #endif |
| } else { |
| eventHandled = true; |
| coreFrame->editor().confirmComposition(eventText); |
| } |
| |
| if (parameters) |
| parameters->eventInterpretationHadSideEffects |= eventHandled; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (void)_updateSecureInputState |
| { |
| if (![[self window] isKeyWindow] || ([[self window] firstResponder] != self && !_private->_forceUpdateSecureInputState)) { |
| if (_private->isInSecureInputState) { |
| DisableSecureEventInput(); |
| _private->isInSecureInputState = NO; |
| } |
| return; |
| } |
| |
| auto* coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return; |
| |
| if (isInPasswordField(coreFrame)) { |
| if (!_private->isInSecureInputState) |
| EnableSecureEventInput(); |
| _private->isInSecureInputState = YES; |
| // WebKit substitutes nil for input context when in password field, which corresponds to null TSMDocument. So, there is |
| // no need to call TSMGetActiveDocument(), which may return an incorrect result when selection hasn't been yet updated |
| // after focusing a node. |
| static CFArrayRef inputSources = TISCreateASCIICapableInputSourceList(); |
| TSMSetDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag, sizeof(CFArrayRef), &inputSources); |
| } else { |
| if (_private->isInSecureInputState) |
| DisableSecureEventInput(); |
| _private->isInSecureInputState = NO; |
| TSMRemoveDocumentProperty(0, kTSMDocumentEnabledInputSourcesPropertyTag); |
| } |
| } |
| |
| - (void)_updateSelectionForInputManager |
| { |
| auto* coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return; |
| |
| BOOL exposeInputContext = isTextInput(coreFrame) && !isInPasswordField(coreFrame); |
| if (exposeInputContext != _private->exposeInputContext) { |
| _private->exposeInputContext = exposeInputContext; |
| // Let AppKit cache a potentially changed input context. |
| // WebCore routinely sets the selection to None when editing, and IMs become unhappy when an input context suddenly turns nil, see bug 26009. |
| if (!coreFrame->selection().isNone()) |
| [NSApp updateWindows]; |
| } |
| |
| [self _updateSecureInputState]; |
| |
| if (!coreFrame->editor().hasComposition() || coreFrame->editor().ignoreSelectionChanges()) |
| return; |
| |
| unsigned start; |
| unsigned end; |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| if (coreFrame->editor().getCompositionSelection(start, end)) |
| [[NSInputManager currentInputManager] markedTextSelectionChanged:NSMakeRange(start, end - start) client:self]; |
| else { |
| coreFrame->editor().cancelComposition(); |
| [[NSInputManager currentInputManager] markedTextAbandoned:self]; |
| } |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| } |
| |
| #endif |
| |
| #if HAVE(TOUCH_BAR) |
| |
| - (NSCandidateListTouchBarItem *)candidateListTouchBarItem |
| { |
| return [[self _webView] candidateList]; |
| } |
| |
| #endif |
| |
| @end |
| |
| @implementation WebHTMLView (WebDocumentPrivateProtocols) |
| |
| - (NSRect)selectionRect |
| { |
| if (![self _hasSelection]) |
| return NSZeroRect; |
| return core([self _frame])->selection().selectionBounds(); |
| } |
| |
| - (NSArray *)selectionTextRects |
| { |
| if (![self _hasSelection]) |
| return nil; |
| |
| Vector<WebCore::FloatRect> rects; |
| if (auto* coreFrame = core([self _frame])) |
| coreFrame->selection().getClippedVisibleTextRectangles(rects); |
| return createNSArray(rects).autorelease(); |
| } |
| |
| - (NSView *)selectionView |
| { |
| return self; |
| } |
| |
| #if PLATFORM(IOS_FAMILY) |
| |
| static CGImageRef imageFromRect(WebCore::Frame* frame, CGRect rect) |
| { |
| auto* page = frame->page(); |
| if (!page) |
| return nil; |
| WAKView* documentView = frame->view()->documentView(); |
| if (!documentView) |
| return nil; |
| if (![documentView isKindOfClass:[WebHTMLView class]]) |
| return nil; |
| |
| WebHTMLView *view = (WebHTMLView *)documentView; |
| |
| OptionSet<WebCore::PaintBehavior> oldPaintBehavior = frame->view()->paintBehavior(); |
| frame->view()->setPaintBehavior(oldPaintBehavior | WebCore::PaintBehavior::FlattenCompositingLayers | WebCore::PaintBehavior::Snapshotting); |
| |
| BEGIN_BLOCK_OBJC_EXCEPTIONS |
| |
| CGRect bounds = [view bounds]; |
| |
| float scale = page->pageScaleFactor() * page->deviceScaleFactor(); |
| |
| // Round image rect size in window coordinate space to avoid pixel cracks at HiDPI (4622794) |
| rect = [view convertRect:rect toView:nil]; |
| rect.size.height = roundf(rect.size.height); |
| rect.size.width = roundf(rect.size.width); |
| rect = [view convertRect:rect fromView:nil]; |
| if (rect.size.width == 0 || rect.size.height == 0) |
| return nil; |
| |
| size_t width = static_cast<size_t>(rect.size.width * scale); |
| size_t height = static_cast<size_t>(rect.size.height * scale); |
| size_t bitsPerComponent = 8; |
| size_t bitsPerPixel = 4 * bitsPerComponent; |
| size_t bytesPerRow = ((bitsPerPixel + 7) / 8) * width; |
| RetainPtr<CGContextRef> context = adoptCF(CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, WebCore::sRGBColorSpaceRef(), kCGImageAlphaPremultipliedLast)); |
| if (!context) |
| return nil; |
| |
| CGContextRef oldContext = WKGetCurrentGraphicsContext(); |
| CGContextRef contextRef = context.get(); |
| WKSetCurrentGraphicsContext(contextRef); |
| |
| CGContextClearRect(contextRef, CGRectMake(0, 0, width, height)); |
| CGContextSaveGState(contextRef); |
| CGContextScaleCTM(contextRef, scale, scale); |
| CGContextSetBaseCTM(contextRef, CGAffineTransformMakeScale(scale, scale)); |
| CGContextTranslateCTM(contextRef, bounds.origin.x - rect.origin.x, bounds.origin.y - rect.origin.y); |
| |
| [view drawSingleRect:rect]; |
| |
| CGContextRestoreGState(contextRef); |
| |
| RetainPtr<CGImageRef> resultImage = adoptCF(CGBitmapContextCreateImage(contextRef)); |
| |
| WKSetCurrentGraphicsContext(oldContext); |
| frame->view()->setPaintBehavior(oldPaintBehavior); |
| |
| return resultImage.autorelease(); |
| |
| END_BLOCK_OBJC_EXCEPTIONS |
| |
| frame->view()->setPaintBehavior(oldPaintBehavior); |
| return nil; |
| } |
| |
| static CGImageRef selectionImage(WebCore::Frame* frame, bool forceBlackText) |
| { |
| ASSERT(!WebThreadIsEnabled() || WebThreadIsLocked()); |
| frame->view()->setPaintBehavior(WebCore::PaintBehavior::SelectionOnly | (forceBlackText ? OptionSet<WebCore::PaintBehavior>(WebCore::PaintBehavior::ForceBlackText) : OptionSet<WebCore::PaintBehavior>())); |
| frame->document()->updateLayout(); |
| CGImageRef result = imageFromRect(frame, frame->selection().selectionBounds()); |
| frame->view()->setPaintBehavior(WebCore::PaintBehavior::Normal); |
| return result; |
| } |
| |
| #endif // PLATFORM(IOS_FAMILY) |
| |
| #if PLATFORM(MAC) |
| - (NSImage *)selectionImageForcingBlackText:(BOOL)forceBlackText |
| #else |
| - (CGImageRef)selectionImageForcingBlackText:(BOOL)forceBlackText |
| #endif |
| { |
| if (![self _hasSelection]) |
| return nil; |
| |
| auto* coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return nil; |
| |
| Ref<WebCore::Frame> protectedCoreFrame(*coreFrame); |
| |
| #if PLATFORM(IOS_FAMILY) |
| return selectionImage(coreFrame, forceBlackText); |
| #else |
| WebCore::TextIndicatorData textIndicator; |
| return createDragImageForSelection(*coreFrame, textIndicator, forceBlackText).autorelease(); |
| #endif |
| } |
| |
| - (NSRect)selectionImageRect |
| { |
| if (![self _hasSelection]) |
| return NSZeroRect; |
| return core([self _frame])->selection().selectionBounds(); |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (NSArray *)pasteboardTypesForSelection |
| { |
| if ([self _canSmartCopyOrDelete]) { |
| auto types = adoptNS([[[self class] _selectionPasteboardTypes] mutableCopy]); |
| [types addObject:WebSmartPastePboardType]; |
| return types.autorelease(); |
| } |
| return [[self class] _selectionPasteboardTypes]; |
| } |
| |
| - (void)writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard |
| { |
| [self _writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard cachedAttributedString:nil]; |
| } |
| |
| #endif |
| |
| - (void)selectAll |
| { |
| auto* coreFrame = core([self _frame]); |
| if (coreFrame) |
| coreFrame->selection().selectAll(); |
| } |
| |
| - (void)deselectAll |
| { |
| auto* coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return; |
| coreFrame->selection().clear(); |
| } |
| |
| - (NSString *)string |
| { |
| return [[self _frame] _stringForRange:[self _documentRange]]; |
| } |
| |
| #if PLATFORM(MAC) |
| |
| - (NSAttributedString *)_legacyAttributedStringFrom:(DOMNode *)startContainer offset:(int)startOffset to:(DOMNode *)endContainer offset:(int)endOffset |
| { |
| if (!startContainer || !endContainer) |
| return adoptNS([[NSAttributedString alloc] init]).autorelease(); |
| return attributedString(WebCore::SimpleRange { { *core(startContainer), static_cast<unsigned>(startOffset) }, |
| { *core(endContainer), static_cast<unsigned>(endOffset) } }).string.autorelease(); |
| } |
| |
| - (NSAttributedString *)attributedString |
| { |
| auto document = core([[self _frame] DOMDocument]); |
| if (!document) |
| return adoptNS([[NSAttributedString alloc] init]).autorelease(); |
| auto range = makeRangeSelectingNodeContents(*document); |
| if (auto result = attributedString(range).string) |
| return result.autorelease(); |
| return editingAttributedString(range).string.autorelease(); |
| } |
| |
| - (NSAttributedString *)selectedAttributedString |
| { |
| auto frame = core([self _frame]); |
| if (!frame) |
| return adoptNS([[NSAttributedString alloc] init]).autorelease(); |
| auto range = frame->selection().selection().firstRange(); |
| if (!range) |
| return adoptNS([[NSAttributedString alloc] init]).autorelease(); |
| if (auto result = attributedString(*range).string) |
| return result.autorelease(); |
| return editingAttributedString(*range).string.autorelease(); |
| } |
| |
| #endif |
| |
| - (NSString *)selectedString |
| { |
| return [[self _frame] _selectedString]; |
| } |
| |
| - (BOOL)supportsTextEncoding |
| { |
| return YES; |
| } |
| |
| - (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag startInSelection:(BOOL)startInSelection |
| { |
| return [self _findString:string options:(forward ? 0 : WebFindOptionsBackwards) | (caseFlag ? 0 : WebFindOptionsCaseInsensitive) | (wrapFlag ? WebFindOptionsWrapAround : 0) | (startInSelection ? WebFindOptionsStartInSelection : 0)]; |
| } |
| |
| @end |
| |
| @implementation WebHTMLView (WebDocumentInternalProtocols) |
| |
| - (NSDictionary *)elementAtPoint:(NSPoint)point |
| { |
| return [self elementAtPoint:point allowShadowContent:NO]; |
| } |
| |
| - (NSDictionary *)elementAtPoint:(NSPoint)point allowShadowContent:(BOOL)allowShadowContent |
| { |
| using namespace WebCore; |
| |
| auto* coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return nil; |
| OptionSet<HitTestRequest::Type> hitType { HitTestRequest::Type::ReadOnly, HitTestRequest::Type::Active, HitTestRequest::Type::AllowChildFrameContent }; |
| if (!allowShadowContent) |
| hitType.add(HitTestRequest::Type::DisallowUserAgentShadowContent); |
| return adoptNS([[WebElementDictionary alloc] initWithHitTestResult:coreFrame->eventHandler().hitTestResultAtPoint(IntPoint { point }, hitType)]).autorelease(); |
| } |
| |
| - (NSUInteger)countMatchesForText:(NSString *)string inDOMRange:(DOMRange *)range options:(WebFindOptions)options limit:(NSUInteger)limit markMatches:(BOOL)markMatches |
| { |
| auto* coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return 0; |
| |
| return coreFrame->editor().countMatchesForText(string, makeSimpleRange(core(range)), coreOptions(options), limit, markMatches, 0); |
| } |
| |
| - (void)setMarkedTextMatchesAreHighlighted:(BOOL)newValue |
| { |
| auto* coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return; |
| coreFrame->editor().setMarkedTextMatchesAreHighlighted(newValue); |
| } |
| |
| - (BOOL)markedTextMatchesAreHighlighted |
| { |
| auto* coreFrame = core([self _frame]); |
| return coreFrame && coreFrame->editor().markedTextMatchesAreHighlighted(); |
| } |
| |
| - (void)unmarkAllTextMatches |
| { |
| auto* coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return; |
| auto* document = coreFrame->document(); |
| if (!document) |
| return; |
| document->markers().removeMarkers(WebCore::DocumentMarker::TextMatch); |
| } |
| |
| - (NSArray *)rectsForTextMatches |
| { |
| auto* coreFrame = core([self _frame]); |
| if (!coreFrame) |
| return @[]; |
| auto* document = coreFrame->document(); |
| if (!document) |
| return @[]; |
| |
| return createNSArray(document->markers().renderedRectsForMarkers(WebCore::DocumentMarker::TextMatch)).autorelease(); |
| } |
| |
| - (BOOL)_findString:(NSString *)string options:(WebFindOptions)options |
| { |
| if (![string length]) |
| return NO; |
| auto* coreFrame = core([self _frame]); |
| return coreFrame && coreFrame->editor().findString(string, coreOptions(options)); |
| } |
| |
| @end |
| |
| #if PLATFORM(MAC) |
| |
| @implementation WebHTMLView (TestingSupportMac) |
| |
| - (BOOL)_secureEventInputEnabledForTesting |
| { |
| return _private->isInSecureInputState; |
| } |
| |
| @end |
| |
| #endif // PLATFORM(MAC) |
| |
| // This is used by AppKit/TextKit. It should be possible to remove this once |
| // -[NSAttributedString _documentFromRange:document:documentAttributes:subresources:] is removed. |
| @implementation NSURL (WebDataURL) |
| |
| + (NSURL *)_web_uniqueWebDataURL |
| { |
| return URL::fakeURLWithRelativePart(emptyString()); |
| } |
| |
| @end |
| |
| #if PLATFORM(MAC) |
| |
| @implementation WebResponderChainSink |
| |
| - (id)initWithResponderChain:(NSResponder *)chain |
| { |
| self = [super init]; |
| _lastResponderInChain = chain; |
| while (NSResponder *next = [_lastResponderInChain nextResponder]) |
| _lastResponderInChain = next; |
| [_lastResponderInChain setNextResponder:self]; |
| return self; |
| } |
| |
| - (void)detach |
| { |
| [_lastResponderInChain setNextResponder:nil]; |
| _lastResponderInChain = nil; |
| } |
| |
| - (BOOL)receivedUnhandledCommand |
| { |
| return _receivedUnhandledCommand; |
| } |
| |
| - (void)noResponderFor:(SEL)selector |
| { |
| _receivedUnhandledCommand = YES; |
| } |
| |
| - (void)doCommandBySelector:(SEL)selector |
| { |
| _receivedUnhandledCommand = YES; |
| } |
| |
| - (BOOL)tryToPerform:(SEL)action with:(id)object |
| { |
| _receivedUnhandledCommand = YES; |
| return YES; |
| } |
| |
| @end |
| |
| #endif |