/* * Copyright (C) 2005-2018 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 "WebNetscapePluginView.h" #import "WebNodeHighlight.h" #import "WebPluginController.h" #import "WebPreferences.h" #import "WebPreferencesPrivate.h" #import "WebResourcePrivate.h" #import "WebSharingServicePickerController.h" #import "WebTextCompletionController.h" #import "WebTypesInternal.h" #import "WebUIDelegatePrivate.h" #import "WebViewInternal.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/ContextMenu.h> #import <WebCore/ContextMenuController.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/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/MainFrame.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/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/NSURLFileTypeMappingsSPI.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 <runtime/InitializeThreading.h> #import <wtf/BlockObjCExceptions.h> #import <wtf/MainThread.h> #import <wtf/MathExtras.h> #import <wtf/ObjcRuntimeExtras.h> #import <wtf/RunLoop.h> #import <wtf/SystemTracing.h> #if PLATFORM(MAC) #import "WebNSEventExtras.h" #import "WebNSPasteboardExtras.h" #import <AppKit/NSAccessibility.h> #import <ApplicationServices/ApplicationServices.h> #import <WebCore/PlatformEventFactoryMac.h> #import <pal/spi/mac/NSMenuSPI.h> #endif #if PLATFORM(IOS) #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/WebEvent.h> #endif using namespace WebCore; using namespace HTMLNames; using namespace WTF; #if PLATFORM(IOS) @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 () - (id)_newFirstResponderAfterResigning; @end @interface NSWindow (WebBorderViewAccess) - (NSView *)_web_borderView; @end using WebEvent = NSEvent; const auto WebEventMouseDown = NSEventTypeLeftMouseDown; @interface WebMenuTarget : NSObject { WebCore::ContextMenuController* _menuController; } + (WebMenuTarget*)sharedMenuTarget; - (WebCore::ContextMenuController*)menuController; - (void)setMenuController:(WebCore::ContextMenuController*)menuController; - (void)forwardContextMenuAction:(id)sender; @end static std::optional<ContextMenuAction> toAction(NSInteger tag) { 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; } return std::nullopt; } static std::optional<NSInteger> toTag(ContextMenuAction action) { 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 ContextMenuItemTagShareMenu: return WebMenuItemTagShareMenu; case ContextMenuItemTagToggleVideoEnhancedFullscreen: return WebMenuItemTagToggleVideoEnhancedFullscreen; 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; } - (WebCore::ContextMenuController*)menuController { return _menuController; } - (void)setMenuController:(WebCore::ContextMenuController*)menuController { _menuController = menuController; } - (void)forwardContextMenuAction:(id)sender { if (auto action = toAction([sender tag])) _menuController->contextMenuItemSelected(*action, [sender title]); } @end @implementation NSWindow (WebBorderViewAccess) - (NSView *)_web_borderView { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" return _borderView; #pragma clang diagnostic pop } @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 _web_borderView] hitTest:point]; forceNSViewHitTest = NO; // WebHTMLView doesn't use cursor rects. if ([view isKindOfClass:[WebHTMLView class]]) return false; #if ENABLE(NETSCAPE_PLUGIN_API) // Neither do NPAPI plug-ins. if ([view isKindOfClass:[WebBaseNetscapePluginView class]]) return false; #endif // 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); } // FIXME: Get this from <AppKit/NSTextInputContext_Private.h> using a NSTextInputContextSPI.h header instead of defining it here. extern "C" NSString *NSTextInputReplacementRangeAttributeName; #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) #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300 - (void)_recursive:(BOOL)recurse displayRectIgnoringOpacity:(NSRect)displayRect inContext:(NSGraphicsContext *)context shouldChangeFontReferenceColor:(BOOL)shouldChangeFontReferenceColor; - (void)_recursive:(BOOL)recurseX displayRectIgnoringOpacity:(NSRect)displayRect inGraphicsContext:(NSGraphicsContext *)graphicsContext shouldChangeFontReferenceColor:(BOOL)shouldChangeFontReferenceColor; #else - (void)_recursive:(BOOL)recurse displayRectIgnoringOpacity:(NSRect)displayRect inContext:(NSGraphicsContext *)context topView:(BOOL)topView; - (void)_recursive:(BOOL)recurseX displayRectIgnoringOpacity:(NSRect)displayRect inGraphicsContext:(NSGraphicsContext *)graphicsContext CGContext:(CGContextRef)ctx topView:(BOOL)isTopView shouldChangeFontReferenceColor:(BOOL)shouldChangeFontReferenceColor; #endif #endif - (void)_setDrawsOwnDescendants:(BOOL)drawsOwnDescendants; #if PLATFORM(IOS) - (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; } Frame* coreFrame = core([enclosingWebFrameView webFrame]); FrameView* frameView = coreFrame ? coreFrame->view() : 0; if (!frameView || !frameView->isEnclosedInCompositingLayer()) { [self _web_setNeedsDisplayInRect:invalidRect]; return; } NSRect invalidRectInWebFrameViewCoordinates = [enclosingWebFrameView convertRect:invalidRect fromView:self]; IntRect invalidRectInFrameViewCoordinates(invalidRectInWebFrameViewCoordinates); if (![enclosingWebFrameView isFlipped]) invalidRectInFrameViewCoordinates.setY(frameView->frameRect().size().height() - invalidRectInFrameViewCoordinates.maxY()); frameView->invalidateRect(invalidRectInFrameViewCoordinates); } @end #endif // PLATFORM(MAC) const float _WebHTMLViewPrintingMinimumShrinkFactor = PrintContext::minimumShrinkFactor(); const float _WebHTMLViewPrintingMaximumShrinkFactor = 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 CachedImageClient& promisedDataClient() { static NeverDestroyed<CachedImageClient> staticCachedResourceClient; return staticCachedResourceClient.get(); } #endif #if PLATFORM(IOS) static NSString * const WebMarkedTextUpdatedNotification = @"WebMarkedTextUpdated"; #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) - (NSView *)_hitViewForEvent:(NSEvent *)event; - (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 #if ENABLE(NETSCAPE_PLUGIN_API) - (void)_web_makePluginSubviewsPerformSelector:(SEL)selector withObject:(id)object; #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) - (void)doCommandBySelector:(SEL)selector; #endif @end @interface NSView (WebHTMLViewFileInternal) - (void)_web_addDescendentWebHTMLViewsToArray:(NSMutableArray *) array; @end struct WebHTMLViewInterpretKeyEventsParameters { 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; 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) 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 FalseTriState: return NSControlStateValueOff; case TrueTriState: return NSControlStateValueOn; case MixedTriState: 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::initializeThreading(); WTF::initializeMainThreadToProcessMainThread(); RunLoop::initializeMainRunLoop(); 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 { NSMutableArray *elements = [[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 (!RuntimeEnabledFeatures::sharedFeatures().attachmentElementEnabled()) [elements addObject:@"object"]; #endif return [elements autorelease]; } - (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard inContext:(DOMRange *)context allowPlainText:(BOOL)allowPlainText { 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:legacyFilenamesPasteboardType()] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:legacyFilenamesPasteboardType() inContext:context subresources:0])) return fragment; if ([types containsObject:legacyHTMLPasteboardType()] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:legacyHTMLPasteboardType() inContext:context subresources:0])) return fragment; if ([types containsObject:legacyRTFDPasteboardType()] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:legacyRTFDPasteboardType() inContext:context subresources:0])) return fragment; if ([types containsObject:legacyRTFPasteboardType()] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:legacyRTFPasteboardType() inContext:context subresources:0])) return fragment; if ([types containsObject:legacyTIFFPasteboardType()] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:legacyTIFFPasteboardType() inContext:context subresources:0])) return fragment; if ([types containsObject:legacyPDFPasteboardType()] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:legacyPDFPasteboardType() inContext:context subresources:0])) return fragment; if ([types containsObject:(NSString *)kUTTypePNG] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:(NSString *)kUTTypePNG inContext:context subresources:0])) return fragment; if ([types containsObject:legacyURLPasteboardType()] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:legacyURLPasteboardType() inContext:context subresources:0])) return fragment; if (allowPlainText && [types containsObject:legacyStringPasteboardType()] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:legacyStringPasteboardType() inContext:context subresources:0])) return fragment; return nil; } - (NSString *)_plainTextFromPasteboard:(NSPasteboard *)pasteboard { NSArray *types = [pasteboard types]; if ([types containsObject:legacyStringPasteboardType()]) return [[pasteboard stringForType:legacyStringPasteboardType()] precomposedStringWithCanonicalMapping]; RetainPtr<NSAttributedString> attributedString; if ([types containsObject:legacyRTFDPasteboardType()]) attributedString = adoptNS([[NSAttributedString alloc] initWithRTFD:[pasteboard dataForType:legacyRTFDPasteboardType()] documentAttributes:NULL]); if (attributedString == nil && [types containsObject:legacyRTFPasteboardType()]) attributedString = adoptNS([[NSAttributedString alloc] initWithRTF:[pasteboard dataForType:legacyRTFPasteboardType()] documentAttributes:NULL]); if (attributedString) return [[[attributedString string] copy] autorelease]; if ([types containsObject:legacyFilenamesPasteboardType()]) { if (NSString *string = [[pasteboard propertyListForType: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]; Frame* 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 { Frame* 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 { Frame* coreFrame = core([self _frame]); return coreFrame ? kit(coreFrame->selection().toNormalizedRange().get()) : nil; } #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; } - (void)_writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard cachedAttributedString:(NSAttributedString *)attributedString { // Put HTML on the pasteboard. if ([types containsObject:WebArchivePboardType]) { if (RefPtr<LegacyWebArchive> coreArchive = LegacyWebArchive::createFromSelection(core([self _frame]))) { if (RetainPtr<CFDataRef> data = coreArchive ? coreArchive->rawDataRepresentation() : 0) [pasteboard setData:(NSData *)data.get() forType:WebArchivePboardType]; } } // Put the attributed string on the pasteboard (RTF/RTFD format). if ([types containsObject:legacyRTFDPasteboardType()]) { if (attributedString == nil) { attributedString = [self selectedAttributedString]; } NSData *RTFDData = [attributedString RTFDFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:@{ }]; [pasteboard setData:RTFDData forType:legacyRTFDPasteboardType()]; } if ([types containsObject:legacyRTFPasteboardType()]) { if (!attributedString) attributedString = [self selectedAttributedString]; if ([attributedString containsAttachments]) attributedString = attributedStringByStrippingAttachmentCharacters(attributedString); NSData *RTFData = [attributedString RTFFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:@{ }]; [pasteboard setData:RTFData forType:legacyRTFPasteboardType()]; } // Put plain string on the pasteboard. if ([types containsObject: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: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) #if ENABLE(NETSCAPE_PLUGIN_API) - (void)_web_makePluginSubviewsPerformSelector:(SEL)selector withObject:(id)object { // Copy subviews because [self subviews] returns the view's mutable internal array, // and we must avoid mutating the array while enumerating it. auto subviewsCopy = adoptNS([self.subviews copy]); for (NSView *view in subviewsCopy.get()) { if ([view isKindOfClass:[WebBaseNetscapePluginView class]]) [view performSelector:selector withObject:object]; } } #endif @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) - (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] #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" convertScreenToBase:[NSEvent mouseLocation]] #pragma clang diagnostic pop 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 (Frame* coreFrame = core([self _frame])) { if (FrameView* coreView = coreFrame->view()) { _private->inScrollPositionChanged = YES; coreView->scrollOffsetChangedViaPlatformWidget(IntPoint(_private->lastScrollPosition), 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); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" _private->savedSubviews = _subviews; #pragma clang diagnostic pop // We need to keep the layer-hosting view in the subviews, otherwise the layers flash. if (_private->layerHostingView) { NSArray* newSubviews = [[NSArray alloc] initWithObjects:_private->layerHostingView, nil]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" _subviews = newSubviews; } else _subviews = nil; #pragma clang diagnostic pop _private->subviewsSetAside = YES; #endif } - (void)_restoreSubviews { #if PLATFORM(MAC) ASSERT(_private->subviewsSetAside); if (_private->layerHostingView) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" [_subviews release]; _subviews = _private->savedSubviews; } else { ASSERT(_subviews == nil); _subviews = _private->savedSubviews; #pragma clang diagnostic pop } _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. #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300 - (void)_recursive:(BOOL)recurse displayRectIgnoringOpacity:(NSRect)displayRect inContext:(NSGraphicsContext *)context shouldChangeFontReferenceColor:(BOOL)shouldChangeFontReferenceColor #else - (void)_recursive:(BOOL)recurse displayRectIgnoringOpacity:(NSRect)displayRect inContext:(NSGraphicsContext *)context topView:(BOOL)topView #endif { [self _setAsideSubviews]; #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300 [super _recursive:recurse displayRectIgnoringOpacity:displayRect inContext:context shouldChangeFontReferenceColor:shouldChangeFontReferenceColor]; #else [super _recursive:recurse displayRectIgnoringOpacity:displayRect inContext:context topView:topView]; #endif [self _restoreSubviews]; } // Don't let AppKit even draw subviews. We take care of that. #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300 - (void)_recursive:(BOOL)recurseX displayRectIgnoringOpacity:(NSRect)displayRect inGraphicsContext:(NSGraphicsContext *)graphicsContext shouldChangeFontReferenceColor:(BOOL)shouldChangeFontReferenceColor #else - (void)_recursive:(BOOL)recurseX displayRectIgnoringOpacity:(NSRect)displayRect inGraphicsContext:(NSGraphicsContext *)graphicsContext CGContext:(CGContextRef)ctx topView:(BOOL)isTopView shouldChangeFontReferenceColor:(BOOL)shouldChangeFontReferenceColor #endif { BOOL didSetAsideSubviews = NO; if (!_private->subviewsSetAside) { [self _setAsideSubviews]; didSetAsideSubviews = YES; } #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300 [super _recursive:recurseX displayRectIgnoringOpacity:displayRect inGraphicsContext:graphicsContext shouldChangeFontReferenceColor:shouldChangeFontReferenceColor]; #else [super _recursive:recurseX displayRectIgnoringOpacity:displayRect inGraphicsContext:graphicsContext CGContext:ctx topView:isTopView shouldChangeFontReferenceColor:shouldChangeFontReferenceColor]; #endif if (didSetAsideSubviews) [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) 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) 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 == nil); _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 == nil); _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 == nil); _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; } } } - (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]; [_private->trackingRectOwner 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]; [_private->trackingRectOwner 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) - (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)data { return [[_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; WebHTMLView *view = nil; if ([hitView isKindOfClass:[WebHTMLView class]]) view = (WebHTMLView *)hitView; if (view) [view retain]; 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 (Frame* lastHitCoreFrame = core([lastHitView _frame])) lastHitCoreFrame->eventHandler().mouseMoved(event, [[self _webView] _pressureEvent]); } lastHitView = view; if (view) { if (Frame* 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) #if ENABLE(DASHBOARD_SUPPORT) || [[self _webView] _dashboardBehavior:WebDashboardBehaviorAlwaysSendMouseEventsToAllWindows] #endif ) { coreFrame->eventHandler().mouseMoved(event, [[self _webView] _pressureEvent]); } else { [self removeAllToolTips]; coreFrame->eventHandler().passMouseMovedEventToScrollbars(event, [[self _webView] _pressureEvent]); } } [view release]; } } + (NSString *)_dummyPasteboardType { return @"Apple WebKit dummy pasteboard type"; } + (NSArray *)_insertablePasteboardTypes { static NSArray *types = nil; if (!types) { types = [[NSArray alloc] initWithObjects:WebArchivePboardType, legacyHTMLPasteboardType(), legacyFilenamesPasteboardType(), legacyTIFFPasteboardType(), legacyPDFPasteboardType(), legacyURLPasteboardType(), legacyRTFDPasteboardType(), legacyRTFPasteboardType(), legacyStringPasteboardType(), legacyColorPasteboardType(), kUTTypePNG, nil]; CFRetain(types); } return types; } + (NSArray *)_selectionPasteboardTypes { // FIXME: We should put data for NSHTMLPboardType on the pasteboard but Microsoft Excel doesn't like our format of HTML (3640423). return [NSArray arrayWithObjects:WebArchivePboardType, legacyRTFDPasteboardType(), legacyRTFPasteboardType(), legacyStringPasteboardType(), nil]; } - (void)pasteboardChangedOwner:(NSPasteboard *)pasteboard { [self setPromisedDragTIFFDataSource:nullptr]; } - (void)pasteboard:(NSPasteboard *)pasteboard provideDataForType:(NSString *)type { if ([type isEqualToString:legacyRTFDPasteboardType()] && [[pasteboard types] containsObject:WebArchivePboardType]) { auto archive = adoptNS([[WebArchive alloc] initWithData:[pasteboard dataForType:WebArchivePboardType]]); [pasteboard _web_writePromisedRTFDFromArchive:archive.get() containsImage:[[pasteboard types] containsObject:legacyTIFFPasteboardType()]]; } else if ([type isEqualToString:legacyTIFFPasteboardType()] && _private->promisedDragTIFFDataSource) { if (auto* image = _private->promisedDragTIFFDataSource->image()) [pasteboard setData:(NSData *)image->tiffRepresentation() forType:legacyTIFFPasteboardType()]; [self setPromisedDragTIFFDataSource:nullptr]; } } - (void)_handleAutoscrollForMouseDragged:(NSEvent *)event { [self autoscroll:event]; [self _startAutoscrollTimer:event]; } #endif // PLATFORM(MAC) #if PLATFORM(IOS) // WAKView override. - (void)layoutIfNeeded { [self _layoutIfNeeded]; } // WAKView override. - (void)setScale:(float)scale { [super setScale:scale]; Frame* coreFrame = core([self _frame]); if (!coreFrame) return; if (Page* page = coreFrame->page()) page->setPageScaleFactor(scale, 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] #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" convertScreenToBase:[NSEvent mouseLocation]] #pragma clang diagnostic pop 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 { Frame* coreFrame = core([self _frame]); return coreFrame && coreFrame->editor().canEdit(); } - (BOOL)_canEditRichly { Frame* coreFrame = core([self _frame]); return coreFrame && coreFrame->editor().canEditRichly(); } - (BOOL)_canAlterCurrentSelection { return [self _hasSelectionOrInsertionPoint] && [self _isEditable]; } - (BOOL)_hasSelection { Frame* coreFrame = core([self _frame]); return coreFrame && coreFrame->selection().selection().isRange(); } - (BOOL)_hasSelectionOrInsertionPoint { Frame* coreFrame = core([self _frame]); return coreFrame && coreFrame->selection().selection().isCaretOrRange(); } - (BOOL)_hasInsertionPoint { Frame* coreFrame = core([self _frame]); return coreFrame && coreFrame->selection().selection().isCaret(); } - (BOOL)_isEditable { Frame* 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; Frame* coreFrame = core([self _frame]); if (!coreFrame) return nil; 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 { Frame* coreFrame = core([self _frame]); return coreFrame ? kit(coreFrame->editor().insertOrderedList().get()) : nil; } - (DOMNode *)_insertUnorderedList { Frame* coreFrame = core([self _frame]); return coreFrame ? kit(coreFrame->editor().insertUnorderedList().get()) : nil; } - (BOOL)_canIncreaseSelectionListLevel { Frame* coreFrame = core([self _frame]); return coreFrame && coreFrame->editor().canIncreaseSelectionListLevel(); } - (BOOL)_canDecreaseSelectionListLevel { Frame* coreFrame = core([self _frame]); return coreFrame && coreFrame->editor().canDecreaseSelectionListLevel(); } - (DOMNode *)_increaseSelectionListLevel { Frame* coreFrame = core([self _frame]); return coreFrame ? kit(coreFrame->editor().increaseSelectionListLevel().get()) : nil; } - (DOMNode *)_increaseSelectionListLevelOrdered { Frame* coreFrame = core([self _frame]); return coreFrame ? kit(coreFrame->editor().increaseSelectionListLevelOrdered().get()) : nil; } - (DOMNode *)_increaseSelectionListLevelUnordered { Frame* coreFrame = core([self _frame]); return coreFrame ? kit(coreFrame->editor().increaseSelectionListLevelUnordered().get()) : nil; } - (void)_decreaseSelectionListLevel { Frame* 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: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 { auto 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:legacyFilenamesPasteboardType()]) return [self _documentFragmentWithPaths:[pasteboard propertyListForType:legacyFilenamesPasteboardType()]]; if ([pboardType isEqualToString:legacyHTMLPasteboardType()]) { NSString *HTMLString = [pasteboard stringForType: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:legacyRTFPasteboardType()] || [pboardType isEqualToString:legacyRTFDPasteboardType()]) { RetainPtr<NSAttributedString> string; if ([pboardType isEqualToString:legacyRTFDPasteboardType()]) string = adoptNS([[NSAttributedString alloc] initWithRTFD:[pasteboard dataForType:legacyRTFDPasteboardType()] documentAttributes:NULL]); if (!string) string = adoptNS([[NSAttributedString alloc] initWithRTF:[pasteboard dataForType: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:legacyTIFFPasteboardType()]) return [self _web_documentFragmentFromPasteboard:pasteboard pasteboardType:legacyTIFFPasteboardType() imageMIMEType:@"image/tiff"]; if ([pboardType isEqualToString:legacyPDFPasteboardType()]) return [self _web_documentFragmentFromPasteboard:pasteboard pasteboardType:legacyPDFPasteboardType() imageMIMEType:@"application/pdf"]; if ([pboardType isEqualToString:(NSString *)kUTTypePNG]) return [self _web_documentFragmentFromPasteboard:pasteboard pasteboardType:(NSString *)kUTTypePNG imageMIMEType:@"image/png"]; if ([pboardType isEqualToString: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:legacyStringPasteboardType()]) { if (!context) return nil; return kit(createFragmentFromText(*core(context), [[pasteboard stringForType:legacyStringPasteboardType()] precomposedStringWithCanonicalMapping]).ptr()); } return nil; } #endif // PLATFORM(MAC) #if ENABLE(NETSCAPE_PLUGIN_API) - (void)_pauseNullEventsForAllNetscapePlugins { [self _web_makePluginSubviewsPerformSelector:@selector(stopTimers) withObject:nil]; } - (void)_resumeNullEventsForAllNetscapePlugins { [self _web_makePluginSubviewsPerformSelector:@selector(restartTimers) withObject:nil]; } #endif - (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 { Frame* 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 { Frame* frame = core([self _frame]); if (!frame) return NO; Document* document = frame->document(); bool isHorizontal = !document || !document->renderView() || document->renderView()->style().isHorizontalWritingMode(); float pageLogicalWidth = isHorizontal ? pageWidth : pageHeight; float pageLogicalHeight = isHorizontal ? pageHeight : pageWidth; 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(FloatSize(pageLogicalWidth, pageLogicalHeight), 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 { Frame* frame = core([self _frame]); if (!frame) return NO; Document* 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; 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(FloatSize(pageLogicalWidth, pageLogicalHeight), 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 { Frame* frame = core([self _frame]); if (!frame) return bottom; FrameView* view = frame->view(); if (!view) return bottom; float newBottom; view->adjustPageHeightDeprecated(&newBottom, top, bottom, bottomLimit); #ifdef __LP64__ // 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; else #endif return newBottom; } #if PLATFORM(IOS) - (id)accessibilityRootElement { return [[self _frame] accessibilityRoot]; } #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::initializeThreading(); WTF::initializeMainThreadToProcessMainThread(); RunLoop::initializeMainRunLoop(); } #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) [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(markedTextUpdate:) name:WebMarkedTextUpdatedNotification object:nil]; #endif #if PLATFORM(MAC) _private->softSpaceRange = NSMakeRange(NSNotFound, 0); #endif return self; } - (void)dealloc { if (WebCoreObjCScheduleDeallocateOnMainThread([WebHTMLView class], self)) return; #if PLATFORM(IOS) [[NSNotificationCenter defaultCenter] removeObserver:self name:WebMarkedTextUpdatedNotification object:nil]; #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"); map->add(@selector(insertParagraphSeparator:), "InsertNewline"); map->add(@selector(insertTabIgnoringFieldEditor:), "InsertTab"); map->add(@selector(pageDown:), "MovePageDown"); map->add(@selector(pageDownAndModifySelection:), "MovePageDownAndModifySelection"); map->add(@selector(pageUp:), "MovePageUp"); map->add(@selector(pageUpAndModifySelection:), "MovePageUpAndModifySelection"); 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); } - (Editor::Command)coreCommandBySelector:(SEL)selector { Frame* coreFrame = core([self _frame]); if (!coreFrame) return Editor::Command(); return coreFrame->editor().command(commandNameForSelector(selector)); } - (Editor::Command)coreCommandByName:(const char*)name { Frame* coreFrame = core([self _frame]); if (!coreFrame) return Editor::Command(); return coreFrame->editor().command(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) 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: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 (Frame* coreFrame = core([self _frame])) coreFrame->selection().revealSelection(SelectionRevealMode::Reveal, ScrollAlignment::alignCenterAlways); } #if PLATFORM(MAC) - (BOOL)validateUserInterfaceItemWithoutDelegate:(id <NSValidatedUserInterfaceItem>)item { SEL action = [item action]; RefPtr<Frame> frame = core([self _frame]); if (!frame) return NO; if (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:) || action == @selector(_changeSpellingFromMenu:) || 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" : "rtl"; [menuItem setState:frame->editor().selectionHasStyle(CSSPropertyDirection, direction)]; } 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(CSSPropertyDirection, "rtl") ? 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]; if (action == @selector(_ignoreSpellingFromMenu:) || action == @selector(_learnSpellingFromMenu:) || 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; Frame* 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; Frame* 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 { #if ENABLE(NETSCAPE_PLUGIN_API) [self _web_makePluginSubviewsPerformSelector:@selector(viewWillMoveToHostWindow:) withObject:hostWindow]; #endif } - (void)viewDidMoveToHostWindow { #if ENABLE(NETSCAPE_PLUGIN_API) [self _web_makePluginSubviewsPerformSelector:@selector(viewDidMoveToHostWindow) withObject:nil]; #endif } - (void)addSubview:(NSView *)view { [super addSubview:view]; if ([WebPluginController isPlugInView:view]) { #if PLATFORM(IOS) 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 (Frame* 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 { Frame* coreFrame = core([self _frame]); if (!coreFrame) return; if (coreFrame->document()) { if (coreFrame->document()->pageCacheState() != Document::NotInPageCache) return; coreFrame->document()->updateStyleIfNeeded(); } if (![self _needsLayout]) return; #ifdef LOG_TIMES double start = CFAbsoluteTimeGetCurrent(); #endif LOG(View, "%@ doing layout", self); if (FrameView* coreView = coreFrame->view()) { if (minPageLogicalWidth > 0.0) { FloatSize pageSize(minPageLogicalWidth, minPageLogicalHeight); FloatSize originalPageSize(originalPageWidth, originalPageHeight); if (coreFrame->document() && coreFrame->document()->renderView() && !coreFrame->document()->renderView()->style().isHorizontalWritingMode()) { pageSize = FloatSize(minPageLogicalHeight, minPageLogicalWidth); originalPageSize = FloatSize(originalPageHeight, originalPageWidth); } coreView->forceLayoutForPagination(pageSize, originalPageSize, maximumShrinkRatio, adjustViewSize ? AdjustViewSize : 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. [[event retain] autorelease]; [super rightMouseUp:event]; if (Frame* 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) { 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 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 (Image* image = hitTestResult.image()) { if (RefPtr<SharedBuffer> buffer = image->data()) [items addObject:adoptNS([[NSImage alloc] initWithData:[NSData dataWithBytes:buffer->data() length:buffer->size()]]).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 HitTestResult&, const Vector<ContextMenuItem>&); static RetainPtr<NSMenuItem> createMenuItem(const HitTestResult& hitTestResult, const ContextMenuItem& item) { if (item.action() == 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 SeparatorType: return [NSMenuItem separatorItem]; case 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<NSMutableArray> createMenuItems(const HitTestResult& hitTestResult, const Vector<ContextMenuItem>& items) { auto menuItems = adoptNS([[NSMutableArray alloc] init]); for (auto& item : items) { if (auto menuItem = createMenuItem(hitTestResult, item)) [menuItems addObject:menuItem.get()]; } return menuItems; } static RetainPtr<NSArray> customMenuFromDefaultItems(WebView *webView, const ContextMenu& defaultMenu) { const auto& hitTestResult = webView.page->contextMenuController().hitTestResult(); auto defaultMenuItems = createMenuItems(hitTestResult, defaultMenu.items()); 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()).autorelease(); } - (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. [[event retain] autorelease]; [_private->completionController endRevertingChange:NO moveLeft:NO]; RefPtr<Frame> coreFrame = core([self _frame]); if (!coreFrame) return nil; Page* 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(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; ContextMenu* 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 == 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 { Frame* coreFrame = core([self _frame]); if (!coreFrame) return; Document* document = coreFrame->document(); if (!document) return; document->setFocusedElement(0); } - (BOOL)isOpaque { return [[self _webView] drawsBackground]; } #if PLATFORM(MAC) - (void)setLayer:(CALayer *)layer { if (Frame* frame = core([self _frame])) { if (FrameView* 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 (Frame* frame = core([self _frame])) { if (frame->document() && frame->document()->pageCacheState() != Document::NotInPageCache) return; if (FrameView* view = frame->view()) view->setNeedsLayout(); } } - (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 (Frame* frame = core([self _frame])) { if (frame->document() && frame->document()->pageCacheState() != Document::NotInPageCache) return; frame->document()->scheduleForcedStyleRecalc(); } } - (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. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" if ([NSGraphicsContext currentContext] == [[self window] graphicsContext] && [webView _needsOneShotDrawingSynchronization]) { #pragma clang diagnostic pop // 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. 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? [[event retain] autorelease]; #endif Frame* 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. [[event retain] autorelease]; NSView *hitView = [self _hitViewForEvent:event]; WebHTMLView *hitHTMLView = [hitView isKindOfClass:[self class]] ? (WebHTMLView *)hitView : nil; #if ENABLE(DASHBOARD_SUPPORT) if ([[self _webView] _dashboardBehavior:WebDashboardBehaviorAlwaysAcceptsFirstMouse]) return YES; #endif if (hitHTMLView) { bool result = false; if (Frame* coreFrame = core([hitHTMLView _frame])) { coreFrame->eventHandler().setActivationEventNumber([event eventNumber]); [hitHTMLView _setMouseDownEvent:event]; if ([hitHTMLView _isSelectionEvent:event]) { #if ENABLE(DRAG_SUPPORT) if (Page* page = coreFrame->page()) result = coreFrame->eventHandler().eventMayStartDrag(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. [[event retain] 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 (Frame* coreFrame = core([hitHTMLView _frame])) { if (Page* page = coreFrame->page()) result = coreFrame->eventHandler().eventMayStartDrag(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. [[event retain] 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) // 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 (Frame* coreframe = core([self _frame])) coreframe->eventHandler().mouseDown(event); #else #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" NSInputManager *currentInputManager = [NSInputManager currentInputManager]; if (![currentInputManager wantsToHandleMouseEvents] || ![currentInputManager handleMouseEvent:event]) { #pragma clang diagnostic pop [_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 (Frame* 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 (Frame* coreframe = core([self _frame])) coreframe->eventHandler().touchEvent(event); } #endif #if ENABLE(DRAG_SUPPORT) && PLATFORM(MAC) - (void)dragImage:(NSImage *)dragImage at:(NSPoint)at offset:(NSSize)offset event:(NSEvent *)event pasteboard:(NSPasteboard *)pasteboard source:(id)source slideBack:(BOOL)slideBack { 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. [[event retain] autorelease]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" NSInputManager *currentInputManager = [NSInputManager currentInputManager]; if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event]) return; #pragma clang diagnostic pop [self retain]; if (!_private->ignoringMouseDraggedEvents) { if (Frame* frame = core([self _frame])) { if (Page* page = frame->page()) page->mainFrame().eventHandler().mouseDragged(event, [[self _webView] _pressureEvent]); } } [self release]; } - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal { ASSERT(![self _webView] || [self _isTopHTMLView]); Page* page = core([self _webView]); if (!page) return NSDragOperationNone; return (NSDragOperation)page->dragController().sourceDragOperation(); } - (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation { ASSERT(![self _webView] || [self _isTopHTMLView]); NSPoint windowImageLoc = [[self window] convertScreenToBase:aPoint]; NSPoint windowMouseLoc = windowImageLoc; if (Page* 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"]); } - (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination { NSFileWrapper *wrapper = nil; NSURL *draggingElementURL = nil; if (auto* tiffResource = _private->promisedDragTIFFDataSource) { if (auto* buffer = tiffResource->resourceBuffer()) { NSURLResponse *response = tiffResource->response().nsURLResponse(); draggingElementURL = [response URL]; wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:buffer->createNSData().get()] autorelease]; 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]); Page* 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] 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 [NSArray arrayWithObject:[path lastPathComponent]]; } // MARK: NSDraggingSource - (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context { ASSERT(![self _webView] || [self _isTopHTMLView]); Page* page = core([self _webView]); if (!page) return NSDragOperationNone; return (NSDragOperation)page->dragController().sourceDragOperation(); } - (void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation { ASSERT(![self _webView] || [self _isTopHTMLView]); NSPoint windowLocation = [self.window convertRectFromScreen:{ screenPoint, NSZeroSize }].origin; if (Page* 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. [[event retain] autorelease]; #endif [self _setMouseDownEvent:nil]; #if PLATFORM(MAC) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" NSInputManager *currentInputManager = [NSInputManager currentInputManager]; if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event]) return; #pragma clang diagnostic pop #endif [self retain]; [self _stopAutoscrollTimer]; if (Frame* frame = core([self _frame])) { if (Page* page = frame->page()) { #if PLATFORM(IOS) 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) && defined(__LP64__) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 101003 NSEvent *lastPressureEvent = [[self _webView] _pressureEvent]; if (event.phase != NSEventPhaseChanged && event.phase != NSEventPhaseBegan && event.phase != NSEventPhaseEnded) return; RefPtr<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(Frame* coreFrame) { if (!coreFrame) return NO; const VisibleSelection& selection = coreFrame->selection().selection(); return !selection.isNone() && selection.isContentEditable(); } #if PLATFORM(MAC) static BOOL isInPasswordField(Frame* coreFrame) { return coreFrame && coreFrame->selection().selection().isInPasswordField(); } #endif static RefPtr<KeyboardEvent> currentKeyboardEvent(Frame* coreFrame) { #if PLATFORM(MAC) NSEvent *event = [NSApp currentEvent]; if (!event) return nullptr; switch ([event type]) { case NSEventTypeKeyDown: { PlatformKeyboardEvent platformEvent = PlatformEventFactory::createPlatformKeyboardEvent(event); platformEvent.disambiguateKeyDownEvent(PlatformEvent::RawKeyDown); return KeyboardEvent::create(platformEvent, coreFrame->document()->defaultView()); } case NSEventTypeKeyUp: return KeyboardEvent::create(PlatformEventFactory::createPlatformKeyboardEvent(event), coreFrame->document()->defaultView()); default: return nullptr; } #else WebEvent *event = [WAKWindow currentEvent]; if (!event) return nullptr; WebEventType type = event.type; if (type == WebEventKeyDown || type == WebEventKeyUp) { Document* document = coreFrame->document(); return KeyboardEvent::create(PlatformEventFactory::createPlatformKeyboardEvent(event), document ? document->defaultView() : 0); } return nullptr; #endif } - (BOOL)becomeFirstResponder { NSSelectionDirection direction = NSDirectSelection; if (![[self _webView] _isPerformingProgrammaticFocus]) direction = [[self window] keyViewSelectionDirection]; #if PLATFORM(MAC) [self _updateFontPanel]; #endif Frame* 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); Page* 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 (Document* document = frame->document()) document->setFocusedElement(0); page->focusController().setInitialFocus(direction == NSSelectingNext ? FocusDirectionForward : FocusDirectionBackward, 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 Frame* coreFrame = core([self _frame]); if (!coreFrame) return resign; #if PLATFORM(IOS) if (Document* document = coreFrame->document()) { document->markers().removeMarkers(DocumentMarker::DictationPhraseWithAlternatives); document->markers().removeMarkers(DocumentMarker::DictationResult); } #endif Page* 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) 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 #if ENABLE(DASHBOARD_SUPPORT) || [[self _webView] _dashboardBehavior:WebDashboardBehaviorAlwaysSendMouseEventsToAllWindows] #endif ) 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; Frame* coreFrame = core([self _frame]); if (coreFrame) { if (FrameView* coreView = coreFrame->view()) coreView->setMediaType(_private->printing ? "print" : "screen"); if (Document* 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 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; Frame* coreFrame = core([self _frame]); if (coreFrame) { Document* 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]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" [[self window] setAutodisplay:YES]; #pragma clang diagnostic pop } - (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]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" [[self window] setAutodisplay:NO]; #pragma clang diagnostic pop [[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 = [NSArray arrayWithObject:[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. [[event retain] 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]; Frame* 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 Frame *coreFrame = core([self _frame]); if (!eventWasSentToWebCore && coreFrame) coreFrame->eventHandler().keyEvent(event); #endif if (callSuper) [super keyDown:event]; else [NSCursor setHiddenUntilMouseMoves:YES]; } - (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. [[event retain] autorelease]; #endif BOOL eventWasSentToWebCore = (_private->keyDownEvent == event); RetainPtr<WebHTMLView> selfProtector = self; Frame* 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. [[event retain] autorelease]; RetainPtr<WebHTMLView> selfProtector = self; Frame* 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(PlatformEventFactory::createPlatformKeyboardEvent(event)); return; } [super flagsChanged:event]; } - (id)accessibilityAttributeValue:(NSString*)attributeName { if ([attributeName isEqualToString: NSAccessibilityChildrenAttribute]) { id accTree = [[self _frame] accessibilityRoot]; if (accTree) return [NSArray arrayWithObject: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) return [accTree accessibilityHitTest:point]; #else #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" NSPoint windowCoord = [[self window] convertScreenToBase:point]; #pragma clang diagnostic pop 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 (Frame* coreFrame = core([self _frame])) coreFrame->selection().revealSelection(SelectionRevealMode::Reveal, ScrollAlignment::alignCenterAlways); } #if PLATFORM(MAC) - (NSData *)_selectionStartFontAttributesAsRTF { Frame* coreFrame = core([self _frame]); auto string = adoptNS([[NSAttributedString alloc] initWithString:@"x" attributes:coreFrame ? coreFrame->editor().fontAttributesForSelectionStart().get() : nil]); return [string RTFFromRange:NSMakeRange(0, [string length]) documentAttributes:@{ }]; } - (NSDictionary *)_fontAttributesFromFontPasteboard { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" NSPasteboard *fontPasteboard = [NSPasteboard pasteboardWithName:NSFontPboard]; #pragma clang diagnostic pop if (fontPasteboard == nil) return nil; NSData *data = [fontPasteboard dataForType: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. NSAttributedString *string = [[[NSAttributedString alloc] initWithRTF:data documentAttributes:NULL] autorelease]; 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 { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" NSColor *rgbColor = [color colorUsingColorSpaceName:NSDeviceRGBColorSpace]; #pragma clang diagnostic pop // 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:(EditAction)undoAction { if (Frame* coreFrame = core([self _frame])) { // FIXME: We shouldn't have to make a copy here. We want callers of this function to work directly with StyleProperties eventually. Ref<MutableStyleProperties> properties(core(style)->copyProperties()); coreFrame->editor().applyStyleToSelection(properties.ptr(), undoAction); } } - (void)_applyEditingStyleToSelection:(Ref<EditingStyle>&&)editingStyle withUndoAction:(EditAction)undoAction { if (Frame* coreFrame = core([self _frame])) coreFrame->editor().applyStyleToSelection(WTFMove(editingStyle), undoAction); } #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. [[event retain] 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 (Frame* frame = core([self _frame])) ret = frame->eventHandler().keyEvent(event); if (ret) [NSCursor setHiddenUntilMouseMoves:YES]; 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. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" NSPasteboard *fontPasteboard = [NSPasteboard pasteboardWithName:NSFontPboard]; #pragma clang diagnostic pop [fontPasteboard declareTypes:[NSArray arrayWithObject:legacyFontPasteboardType()] owner:nil]; [fontPasteboard setData:[self _selectionStartFontAttributesAsRTF] forType: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:EditActionPasteFont]; } - (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]; } - (NSFont *)_originalFontA { return [[NSFontManager sharedFontManager] fontWithFamily:@"Helvetica" traits:0 weight:STANDARD_WEIGHT size:10.0f]; } - (NSFont *)_originalFontB { return [[NSFontManager sharedFontManager] fontWithFamily:@"Times" traits:NSFontItalicTrait weight:STANDARD_BOLD_WEIGHT size:12.0f]; } static RetainPtr<CFStringRef> fontNameForDescription(NSString *familyName, BOOL italic, BOOL bold) { // Find the font the same way the rendering code would later if it encountered this CSS. FontDescription fontDescription; fontDescription.setIsItalic(italic); fontDescription.setWeight(bold ? FontSelectionValue(900) : FontSelectionValue(500)); RefPtr<Font> font = FontCache::singleton().fontForFamily(fontDescription, familyName); return adoptCF(CTFontCopyPostScriptName(font->getCTFont())); } - (void)_addToStyle:(DOMCSSStyleDeclaration *)style fontA:(NSFont *)a fontB:(NSFont *)b { // Since there's no way to directly ask NSFontManager what style change it's going to do // we instead pass two "specimen" fonts to it and let it change them. We then deduce what // style change it was doing by looking at what happened to each of the two fonts. // So if it was making the text bold, both fonts will be bold after the fact. if (a == nil || b == nil) return; NSFontManager *fm = [NSFontManager sharedFontManager]; NSFont *oa = [self _originalFontA]; NSString *aFamilyName = [a familyName]; NSString *bFamilyName = [b familyName]; int aPointSize = (int)[a pointSize]; int bPointSize = (int)[b pointSize]; int aWeight = [fm weightOfFont:a]; int bWeight = [fm weightOfFont:b]; BOOL aIsItalic = ([fm traitsOfFont:a] & NSItalicFontMask) != 0; BOOL bIsItalic = ([fm traitsOfFont:b] & NSItalicFontMask) != 0; BOOL aIsBold = aWeight > MIN_BOLD_WEIGHT; if ([aFamilyName isEqualToString:bFamilyName]) { NSString *familyNameForCSS = aFamilyName; // The family name may not be specific enough to get us the font specified. // In some cases, the only way to get exactly what we are looking for is to use // the Postscript name. // If we don't find a font with the same Postscript name, then we'll have to use the // Postscript name to make the CSS specific enough. auto fontName = fontNameForDescription(aFamilyName, aIsItalic, aIsBold); auto aName = [a fontName]; if (!fontName || !aName || !CFEqual(fontName.get(), static_cast<CFStringRef>(aName))) familyNameForCSS = aName; // 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:@"'%@'", familyNameForCSS]]; } int soa = (int)[oa pointSize]; if (aPointSize == bPointSize) [style setFontSize:[NSString stringWithFormat:@"%dpx", aPointSize]]; else if (aPointSize < soa) [style _setFontSizeDelta:@"-1px"]; else if (aPointSize > soa) [style _setFontSizeDelta:@"1px"]; // FIXME: Map to the entire range of CSS weight values. if (aWeight == bWeight) [style setFontWeight:aIsBold ? @"bold" : @"normal"]; if (aIsItalic == bIsItalic) [style setFontStyle:aIsItalic ? @"italic" : @"normal"]; } - (DOMCSSStyleDeclaration *)_styleFromFontManagerOperation { DOMCSSStyleDeclaration *style = [self _emptyStyle]; NSFontManager *fm = [NSFontManager sharedFontManager]; NSFont *oa = [self _originalFontA]; NSFont *ob = [self _originalFontB]; [self _addToStyle:style fontA:[fm convertFont:oa] fontB:[fm convertFont:ob]]; return style; } - (void)changeFont:(id)sender { COMMAND_PROLOGUE [self _applyStyleToSelection:[self _styleFromFontManagerOperation] withUndoAction:EditActionSetFont]; } - (Ref<EditingStyle>)_styleForAttributeChange:(id)sender { DOMCSSStyleDeclaration *style = [self _emptyStyle]; auto shadow = adoptNS([[NSShadow alloc] init]); [shadow setShadowOffset:NSMakeSize(1, 1)]; NSDictionary *oa = [NSDictionary dictionaryWithObjectsAndKeys: [self _originalFontA], NSFontAttributeName, nil]; NSDictionary *ob = [NSDictionary dictionaryWithObjectsAndKeys: [NSColor blackColor], NSBackgroundColorAttributeName, [self _originalFontB], NSFontAttributeName, [NSColor whiteColor], NSForegroundColorAttributeName, shadow.get(), NSShadowAttributeName, [NSNumber numberWithInt:NSUnderlineStyleSingle], NSStrikethroughStyleAttributeName, [NSNumber numberWithInt:1], NSSuperscriptAttributeName, [NSNumber numberWithInt:NSUnderlineStyleSingle], NSUnderlineStyleAttributeName, nil]; NSDictionary *a = [sender convertAttributes:oa]; NSDictionary *b = [sender convertAttributes:ob]; NSColor *ca = [a objectForKey:NSBackgroundColorAttributeName]; NSColor *cb = [b objectForKey:NSBackgroundColorAttributeName]; if (ca == cb) { [style setBackgroundColor:[self _colorAsString:ca]]; } [self _addToStyle:style fontA:[a objectForKey:NSFontAttributeName] fontB:[b objectForKey:NSFontAttributeName]]; ca = [a objectForKey:NSForegroundColorAttributeName]; cb = [b objectForKey:NSForegroundColorAttributeName]; if (ca == cb) { if (!ca) ca = [NSColor blackColor]; [style setColor:[self _colorAsString:ca]]; } NSShadow *sha = [a objectForKey:NSShadowAttributeName]; if (sha) [style setTextShadow:[self _shadowAsString:sha]]; else if ([b objectForKey:NSShadowAttributeName] == nil) [style setTextShadow:@"none"]; int sa = [[a objectForKey:NSSuperscriptAttributeName] intValue]; int sb = [[b objectForKey:NSSuperscriptAttributeName] intValue]; if (sa == sb) { if (sa > 0) [style setVerticalAlign:@"super"]; else if (sa < 0) [style setVerticalAlign:@"sub"]; else [style setVerticalAlign:@"baseline"]; } auto editingStyle = EditingStyle::create(core(style)); int strikeThroughA = [[a objectForKey:NSStrikethroughStyleAttributeName] intValue]; int strikeThroughB = [[b objectForKey:NSStrikethroughStyleAttributeName] intValue]; if (strikeThroughA == strikeThroughB) { bool shouldRemoveStrikeThrough = strikeThroughA == NSUnderlineStyleNone; editingStyle->setStrikeThroughChange(shouldRemoveStrikeThrough ? TextDecorationChange::Remove : TextDecorationChange::Add); } int ua = [[a objectForKey:NSUnderlineStyleAttributeName] intValue]; int ub = [[b objectForKey:NSUnderlineStyleAttributeName] intValue]; if (ua == ub) { bool shouldRemoveUnderline = ua == NSUnderlineStyleNone; editingStyle->setUnderlineChange(shouldRemoveUnderline ? TextDecorationChange::Remove : TextDecorationChange::Add); } return editingStyle; } - (void)changeAttributes:(id)sender { COMMAND_PROLOGUE [self _applyEditingStyleToSelection:[self _styleForAttributeChange:sender] withUndoAction:EditActionChangeAttributes]; } - (DOMCSSStyleDeclaration *)_styleFromColorPanelWithSelector:(SEL)selector { DOMCSSStyleDeclaration *style = [self _emptyStyle]; ASSERT([style respondsToSelector:selector]); [style performSelector:selector withObject:[self _colorAsString:[[NSColorPanel sharedColorPanel] color]]]; return style; } - (EditAction)_undoActionFromColorPanelWithSelector:(SEL)selector { if (selector == @selector(setBackgroundColor:)) return EditActionSetBackgroundColor; return EditActionSetColor; } - (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 (Frame* coreFrame = core([self _frame])) { // FIXME: We shouldn't have to make a copy here. Ref<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]]; } - (void)changeColor:(id)sender { 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:EditActionSetColor]; } #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 (Frame* 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 (Frame* 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; Frame* coreFrame = core([self _frame]); if (!coreFrame) return; WritingDirection direction = RightToLeftWritingDirection; switch (coreFrame->editor().baseWritingDirectionForSelectionStart()) { case LeftToRightWritingDirection: break; case RightToLeftWritingDirection: direction = LeftToRightWritingDirection; 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 NaturalWritingDirection: ASSERT_NOT_REACHED(); break; } if (Frame* 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 (Frame* coreFrame = core([self _frame])) coreFrame->editor().setBaseWritingDirection(writingDirection == NSWritingDirectionLeftToRight ? LeftToRightWritingDirection : RightToLeftWritingDirection); } static BOOL writingDirectionKeyBindingsEnabled() { return YES; } - (void)_changeBaseWritingDirectionTo:(NSWritingDirection)direction { if (![self _canEdit]) return; static BOOL bindingsEnabled = writingDirectionKeyBindingsEnabled(); if (!bindingsEnabled) { NSBeep(); return; } if (Frame* coreFrame = core([self _frame])) coreFrame->editor().setBaseWritingDirection(direction == NSWritingDirectionLeftToRight ? LeftToRightWritingDirection : RightToLeftWritingDirection); } - (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 { Frame* frame = core([self _frame]); if (!frame) return; FrameView* 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 @end @implementation WebHTMLView (WebInternal) - (void)_selectionChanged { #if PLATFORM(MAC) [self _updateSelectionForInputManager]; [self _updateFontPanel]; if (Frame* 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 (Frame* coreFrame = core([self _frame])) { if (const Font* fd = coreFrame->editor().fontForSelection(multipleFonts)) font = (NSFont *)fd->platformData().registeredFont(); attributes = coreFrame->editor().fontAttributesForSelectionStart(); } // 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; Frame* coreFrame = core([self _frame]); return coreFrame && coreFrame->selection().granularity() == 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 { Frame* coreFrame = core([self _frame]); if (!coreFrame) return; RefPtr<Range> selectionRange = coreFrame->selection().selection().firstRange(); if (!selectionRange) return; [[self _webView] _showDictionaryLookupPopup:[WebImmediateActionController _dictionaryPopupInfoForRange:*selectionRange inFrame:coreFrame withLookupOptions:nil indicatorOptions:TextIndicatorOptionIncludeSnapshotWithSelectionHighlight transition:TextIndicatorPresentationTransition::BounceAndCrossfade]]; } - (void)quickLookWithEvent:(NSEvent *)event { [[self _webView] _clearTextIndicatorWithAnimation:TextIndicatorWindowDismissalAnimation::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<KeypressCommand>& commands = parameters->event->keypressCommands(); for (size_t i = 0; i < commands.size(); ++i) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" if (commands[i].commandName == "insertText:") [self insertText:commands[i].text]; else if (commands[i].commandName == "noop:") ; // Do nothing. This case can be removed once <rdar://problem/9025012> is fixed. else [self doCommandBySelector:NSSelectorFromString(commands[i].commandName)]; #pragma clang diagnostic pop } parameters->event->keypressCommands().clear(); parameters->shouldSaveCommands = wasSavingCommands; parameters->executingSavedKeypressCommands = false; } #if PLATFORM(MAC) - (BOOL)_interpretKeyEvent:(KeyboardEvent*)event savingCommands:(BOOL)savingCommands { ASSERT(core([self _frame]) == downcast<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(); const PlatformKeyboardEvent* platformEvent = event->keyEvent(); 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<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:[NSArray arrayWithObject: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() == 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) #define kWebEnterKey 0x0003 #define kWebBackspaceKey 0x0008 #define kWebReturnKey 0x000d #define kWebDeleteKey 0x007F #define kWebDeleteForwardKey 0xF728 - (BOOL)_handleEditingKeyEvent:(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 (const PlatformKeyboardEvent* platformEvent = wcEvent->keyEvent()) { WebEvent *event = platformEvent->event(); if (![[self _webView] isEditable] && event.isTabKey) return NO; NSString *s = [event characters]; if (!s.length) return NO; WebView* webView = [self _webView]; switch ([s characterAtIndex:0]) { case kWebBackspaceKey: case kWebDeleteKey: [[webView _UIKitDelegateForwarder] deleteFromInputWithFlags:event.keyboardFlags]; return YES; case kWebEnterKey: case kWebReturnKey: if (platformEvent->type() == PlatformKeyboardEvent::Char) { // Map \r from HW keyboard to \n to match the behavior of the soft keyboard. [[webView _UIKitDelegateForwarder] addInputString:@"\n" withFlags:0]; return YES; } break; case kWebDeleteForwardKey: [self deleteForward:self]; return YES; default: if (platformEvent->type() == PlatformKeyboardEvent::Char) { [[webView _UIKitDelegateForwarder] addInputString:event.characters withFlags:event.keyboardFlags]; return YES; } } } return NO; } #endif // PLATFORM(IOS) #if PLATFORM(MAC) - (void)setPromisedDragTIFFDataSource:(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]; Frame* 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)]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" CGPoint layerPosition = NSPointToCGPoint([self convertPointToBase:[_private->layerHostingView frame].origin]); #pragma clang diagnostic pop [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) - (NSArray *)validAttributesForMarkedText { static NSArray *validAttributes = [[NSArray alloc] initWithObjects: NSUnderlineStyleAttributeName, NSUnderlineColorAttributeName, NSMarkedClauseSegmentAttributeName, NSTextInputReplacementRangeAttributeName, #if USE(DICTATION_ALTERNATIVES) NSTextAlternativesAttributeName, #endif #if USE(INSERTION_UNDO_GROUPING) NSTextInsertionUndoableAttributeName, #endif 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; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" NSAttributedString *result = [self attributedSubstringFromRange:NSMakeRange(0, UINT_MAX)]; #pragma clang diagnostic pop 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 : [[[NSAttributedString alloc] initWithString:@""] autorelease]; } #endif // PLATFORM(MAC) - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint { [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; } - (NSRect)firstRectForCharacterRange:(NSRange)theRange { [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 NSMakeRect(0, 0, 0, 0); } 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; } - (NSRange)selectedRange { [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; } - (NSRange)markedRange { [self _executeSavedKeypressCommands]; WebFrame *webFrame = [self _frame]; Frame* coreFrame = core(webFrame); if (!coreFrame) return NSMakeRange(0, 0); NSRange result = [webFrame _convertToNSRange:coreFrame->editor().compositionRange().get()]; LOG(TextInput, "markedRange -> (%u, %u)", result.location, result.length); return result; } #if PLATFORM(MAC) - (NSAttributedString *)attributedSubstringFromRange:(NSRange)nsRange { [self _executeSavedKeypressCommands]; WebFrame *frame = [self _frame]; Frame* coreFrame = core(frame); if (!isTextInput(coreFrame) || isInPasswordField(coreFrame)) { LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> nil", nsRange.location, nsRange.length); return nil; } RefPtr<Range> range = [frame _convertToDOMRange:nsRange]; if (!range) { LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> nil", nsRange.location, nsRange.length); return nil; } NSAttributedString *result = editingAttributedStringFromRange(*range); // 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; } #endif - (NSInteger)conversationIdentifier { return (NSInteger)self; } - (BOOL)hasMarkedText { Frame* 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; } - (void)unmarkText { [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 (Frame* coreFrame = core([self _frame])) coreFrame->editor().confirmComposition(); } #if PLATFORM(MAC) static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnderline>& result) { int length = [[string string] length]; int i = 0; while (i < length) { NSRange range; NSDictionary *attrs = [string attributesAtIndex:i longestEffectiveRange:&range inRange:NSMakeRange(i, length - i)]; if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) { Color color = Color::black; CompositionUnderlineColor compositionUnderlineColor = CompositionUnderlineColor::TextColor; if (NSColor *colorAttr = [attrs objectForKey:NSUnderlineColorAttributeName]) { color = colorFromNSColor(colorAttr); compositionUnderlineColor = CompositionUnderlineColor::GivenColor; } result.append(CompositionUnderline(range.location, NSMaxRange(range), compositionUnderlineColor, color, [style intValue] > 1)); } i = range.location + range.length; } } #endif - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange { [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; } Frame* coreFrame = core([self _frame]); if (!coreFrame) return; if (![self _isEditable]) return; Vector<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(CompositionUnderline(0, [text length], CompositionUnderlineColor::TextColor, Color::black, false)); } #else text = string; #endif if (replacementRange.location != NSNotFound) [[self _frame] _selectNSRange:replacementRange]; coreFrame->editor().setComposition(text, underlines, newSelRange.location, NSMaxRange(newSelRange)); } #if PLATFORM(IOS) - (void)markedTextUpdate:(NSNotification *)notification { NSString *text = [notification object]; NSRange range = NSMakeRange(0, [text length]); [self setMarkedText:text selectedRange:range]; } #endif - (void)doCommandBySelector:(SEL)selector { 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; KeyboardEvent* 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<Frame> coreFrame = core([self _frame]); bool isFromInputMethod = coreFrame && coreFrame->editor().hasComposition(); if (event && shouldSaveCommand && !isFromInputMethod) event->keypressCommands().append(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 { Editor::Command command = [self coreCommandBySelector:selector]; if (command.isSupported()) eventWasHandled = command.execute(event); #if PLATFORM(MAC) else { // 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; } #endif } if (parameters) parameters->eventInterpretationHadSideEffects |= eventWasHandled; _private->interpretKeyEventsParameters = parameters; } } - (void)insertText:(id)string { #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<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<DictationAlternative> dictationAlternativeLocations; #if PLATFORM(MAC) if (isAttributedString) { #if USE(DICTATION_ALTERNATIVES) Vector<WebCore::TextAlternativeWithRange> textAlternatives; collectDictationTextAlternatives(string, textAlternatives); if (!textAlternatives.isEmpty()) [[self _webView] _getWebCoreDictationAlternatives:dictationAlternativeLocations fromTextAlternatives:textAlternatives]; #endif #if USE(INSERTION_UNDO_GROUPING) registerUndoGroup = shouldRegisterInsertionUndoGroup(string); #endif // FIXME: We ignore most attributes from the string, so for example inserting from Character Palette loses font and glyph variation data. // 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; KeyboardEvent* 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) { event->keypressCommands().append(KeypressCommand("insertText:", text)); return; } if (!coreFrame || !coreFrame->editor().canEdit()) return; BOOL needToRemoveSoftSpace = NO; #if HAVE(ADVANCED_SPELL_CHECKING) 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; } #endif #if PLATFORM(MAC) _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]) { coreFrame->selection().setSelection(VisibleSelection(*domRange, SEL_DEFAULT_AFFINITY)); replacesText = replacementRange.length; } } bool eventHandled = false; String eventText = text; eventText.replace(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 ? TextEventInputAutocompletion : TextEventInputKeyboard); #if USE(INSERTION_UNDO_GROUPING) if (registerUndoGroup) 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; } Frame* 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 { Frame* 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; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" if (coreFrame->editor().getCompositionSelection(start, end)) [[NSInputManager currentInputManager] markedTextSelectionChanged:NSMakeRange(start, end - start) client:self]; else { coreFrame->editor().cancelComposition(); [[NSInputManager currentInputManager] markedTextAbandoned:self]; } #pragma clang diagnostic pop } #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<FloatRect> list; if (Frame* coreFrame = core([self _frame])) coreFrame->selection().getClippedVisibleTextRectangles(list); size_t size = list.size(); NSMutableArray *result = [NSMutableArray arrayWithCapacity:size]; for (size_t i = 0; i < size; ++i) [result addObject:[NSValue valueWithRect:list[i]]]; return result; } - (NSView *)selectionView { return self; } #if PLATFORM(IOS) static CGImageRef imageFromRect(Frame* frame, CGRect rect) { Page* 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; PaintBehavior oldPaintBehavior = frame->view()->paintBehavior(); frame->view()->setPaintBehavior(oldPaintBehavior | PaintBehaviorFlattenCompositingLayers | PaintBehaviorSnapshotting); 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, 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(Frame* frame, bool forceBlackText) { ASSERT(!WebThreadIsEnabled() || WebThreadIsLocked()); frame->view()->setPaintBehavior(PaintBehaviorSelectionOnly | (forceBlackText ? PaintBehaviorForceBlackText : 0)); frame->document()->updateLayout(); CGImageRef result = imageFromRect(frame, frame->selection().selectionBounds()); frame->view()->setPaintBehavior(PaintBehaviorNormal); return result; } #endif // PLATFORM(IOS) #if PLATFORM(MAC) - (NSImage *)selectionImageForcingBlackText:(BOOL)forceBlackText #else - (CGImageRef)selectionImageForcingBlackText:(BOOL)forceBlackText #endif { if (![self _hasSelection]) return nil; Frame* coreFrame = core([self _frame]); if (!coreFrame) return nil; #if PLATFORM(IOS) return selectionImage(coreFrame, forceBlackText); #else 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]) { NSMutableArray *types = [[[[self class] _selectionPasteboardTypes] mutableCopy] autorelease]; [types addObject:WebSmartPastePboardType]; return types; } return [[self class] _selectionPasteboardTypes]; } - (void)writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard { [self _writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard cachedAttributedString:nil]; } #endif - (void)selectAll { Frame* coreFrame = core([self _frame]); if (coreFrame) coreFrame->selection().selectAll(); } - (void)deselectAll { Frame* coreFrame = core([self _frame]); if (!coreFrame) return; coreFrame->selection().clear(); } - (NSString *)string { return [[self _frame] _stringForRange:[self _documentRange]]; } #if PLATFORM(MAC) - (NSAttributedString *)_attributedStringFromDOMRange:(DOMRange *)range { NSAttributedString *attributedString; #if !LOG_DISABLED double start = CFAbsoluteTimeGetCurrent(); #endif if (!range) return nullptr; attributedString = attributedStringFromRange(*core(range)); #if !LOG_DISABLED double duration = CFAbsoluteTimeGetCurrent() - start; LOG(Timing, "creating attributed string from selection took %f seconds.", duration); #endif return attributedString; } - (NSAttributedString *)attributedString { DOMDocument *document = [[self _frame] DOMDocument]; NSAttributedString *attributedString = [self _attributedStringFromDOMRange:[document _documentRange]]; if (!attributedString) { Document* coreDocument = core(document); attributedString = editingAttributedStringFromRange(Range::create(*coreDocument, coreDocument, 0, coreDocument, coreDocument->countChildNodes())); } return attributedString; } - (NSAttributedString *)selectedAttributedString { NSAttributedString *attributedString = [self _attributedStringFromDOMRange:[self _selectedRange]]; if (!attributedString) { Frame* coreFrame = core([self _frame]); if (coreFrame) { RefPtr<Range> range = coreFrame->selection().selection().toNormalizedRange(); if (range) attributedString = editingAttributedStringFromRange(*range); else attributedString = [[[NSAttributedString alloc] init] autorelease]; } } return attributedString; } #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)allow { Frame* coreFrame = core([self _frame]); if (!coreFrame) return nil; HitTestRequest::HitTestRequestType hitType = HitTestRequest::ReadOnly | HitTestRequest::Active | (allow ? 0 : HitTestRequest::DisallowUserAgentShadowContent); return [[[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 { Frame* coreFrame = core([self _frame]); if (!coreFrame) return 0; return coreFrame->editor().countMatchesForText(string, core(range), coreOptions(options), limit, markMatches, 0); } - (void)setMarkedTextMatchesAreHighlighted:(BOOL)newValue { Frame* coreFrame = core([self _frame]); if (!coreFrame) return; coreFrame->editor().setMarkedTextMatchesAreHighlighted(newValue); } - (BOOL)markedTextMatchesAreHighlighted { Frame* coreFrame = core([self _frame]); return coreFrame && coreFrame->editor().markedTextMatchesAreHighlighted(); } - (void)unmarkAllTextMatches { Frame* coreFrame = core([self _frame]); if (!coreFrame) return; Document* document = coreFrame->document(); if (!document) return; document->markers().removeMarkers(DocumentMarker::TextMatch); } - (NSArray *)rectsForTextMatches { Frame* coreFrame = core([self _frame]); if (!coreFrame) return [NSArray array]; Document* document = coreFrame->document(); if (!document) return [NSArray array]; Vector<FloatRect> rects = document->markers().renderedRectsForMarkers(DocumentMarker::TextMatch); unsigned count = rects.size(); NSMutableArray *result = [NSMutableArray arrayWithCapacity:count]; for (unsigned index = 0; index < count; ++index) [result addObject:[NSValue valueWithRect:rects[index]]]; return result; } - (BOOL)_findString:(NSString *)string options:(WebFindOptions)options { if (![string length]) return NO; Frame* coreFrame = core([self _frame]); return coreFrame && coreFrame->editor().findString(string, coreOptions(options)); } @end // 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