diff options
Diffstat (limited to 'WebKit/mac/WebView/WebHTMLView.mm')
-rw-r--r-- | WebKit/mac/WebView/WebHTMLView.mm | 1133 |
1 files changed, 595 insertions, 538 deletions
diff --git a/WebKit/mac/WebView/WebHTMLView.mm b/WebKit/mac/WebView/WebHTMLView.mm index c4ca174..d58c765 100644 --- a/WebKit/mac/WebView/WebHTMLView.mm +++ b/WebKit/mac/WebView/WebHTMLView.mm @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2005, 2006, 2007, 2008, 2009 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 @@ -29,14 +29,17 @@ #import "WebHTMLView.h" +#import "DOMCSSStyleDeclarationInternal.h" +#import "DOMDocumentFragmentInternal.h" +#import "DOMDocumentInternal.h" #import "DOMNodeInternal.h" #import "DOMRangeInternal.h" #import "WebArchive.h" -#import "WebNetscapePluginView.h" #import "WebClipView.h" -#import "WebDOMOperationsPrivate.h" +#import "WebDOMOperationsInternal.h" #import "WebDataSourceInternal.h" #import "WebDefaultUIDelegate.h" +#import "WebDelegateImplementationCaching.h" #import "WebDocumentInternal.h" #import "WebDynamicScrollBarsView.h" #import "WebEditingDelegate.h" @@ -50,7 +53,6 @@ #import "WebKitNSStringExtras.h" #import "WebKitVersionChecks.h" #import "WebLocalizableStrings.h" -#import "WebNodeHighlight.h" #import "WebNSAttributedStringExtras.h" #import "WebNSEventExtras.h" #import "WebNSFileManagerExtras.h" @@ -60,17 +62,20 @@ #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 "WebStringTruncator.h" +#import "WebTextCompletionController.h" #import "WebTypesInternal.h" #import "WebUIDelegatePrivate.h" #import "WebViewInternal.h" #import <AppKit/NSAccessibility.h> #import <ApplicationServices/ApplicationServices.h> -#import <dlfcn.h> +#import <WebCore/CSSMutableStyleDeclaration.h> #import <WebCore/CachedImage.h> #import <WebCore/CachedResourceClient.h> #import <WebCore/ColorMac.h> @@ -78,38 +83,38 @@ #import <WebCore/ContextMenuController.h> #import <WebCore/Document.h> #import <WebCore/DocumentFragment.h> +#import <WebCore/DragController.h> #import <WebCore/Editor.h> #import <WebCore/EditorDeleteAction.h> #import <WebCore/Element.h> #import <WebCore/EventHandler.h> #import <WebCore/ExceptionHandlers.h> -#import <WebCore/DragController.h> #import <WebCore/FloatRect.h> #import <WebCore/FocusController.h> #import <WebCore/Frame.h> #import <WebCore/FrameLoader.h> #import <WebCore/FrameView.h> -#import <WebCore/HitTestResult.h> #import <WebCore/HTMLNames.h> +#import <WebCore/HitTestResult.h> #import <WebCore/Image.h> #import <WebCore/KeyboardEvent.h> #import <WebCore/LegacyWebArchive.h> #import <WebCore/MIMETypeRegistry.h> #import <WebCore/Page.h> #import <WebCore/PlatformKeyboardEvent.h> -#import <WebCore/PlatformMouseEvent.h> #import <WebCore/Range.h> #import <WebCore/SelectionController.h> #import <WebCore/SharedBuffer.h> #import <WebCore/SimpleFontData.h> #import <WebCore/Text.h> #import <WebCore/WebCoreObjCExtras.h> -#import <WebCore/WebCoreTextRenderer.h> +#import <WebCore/WebFontCache.h> #import <WebCore/markup.h> #import <WebKit/DOM.h> #import <WebKit/DOMExtensions.h> #import <WebKit/DOMPrivate.h> #import <WebKitSystemInterface.h> +#import <dlfcn.h> #import <limits> #import <runtime/InitializeThreading.h> @@ -144,6 +149,7 @@ using namespace WTF; static IMP oldSetCursorIMP = NULL; #ifdef BUILDING_ON_TIGER + static IMP oldResetCursorRectsIMP = NULL; static BOOL canSetCursor = YES; @@ -169,7 +175,9 @@ static void setCursor(NSCursor* self, SEL cmd) if (canSetCursor) oldSetCursorIMP(self, cmd); } + #else + static void setCursor(NSWindow* self, SEL cmd, NSPoint point) { NSView* view = [[self _web_borderView] hitTest:point]; @@ -184,21 +192,8 @@ static void setCursor(NSWindow* self, SEL cmd, NSPoint point) } oldSetCursorIMP(self, cmd, point); } -#endif -#if USE(ACCELERATED_COMPOSITING) -@interface WebLayerHostingView : NSView -@end - -@implementation WebLayerHostingView -// Empty NSViews intercept rightMouseDown: to do context menu handling, but we need the WebLayerHostingView to -// let right mouse clicks through. -- (void)rightMouseDown:(NSEvent *)theEvent -{ - [[self nextResponder] performSelector:_cmd withObject:theEvent]; -} -@end -#endif // USE(ACCELERATED_COMPOSITING) +#endif extern "C" { @@ -206,6 +201,7 @@ extern "C" { extern NSString *NSMarkedClauseSegmentAttributeName; extern NSString *NSTextInputReplacementRangeAttributeName; + } @interface NSView (WebNSViewDetails) @@ -224,7 +220,6 @@ extern NSString *NSTextInputReplacementRangeAttributeName; @interface NSWindow (WebNSWindowDetails) - (id)_newFirstResponderAfterResigning; -- (void)_setForceActiveControls:(BOOL)flag; @end @interface NSAttributedString (WebNSAttributedStringDetails) @@ -335,6 +330,32 @@ static CachedResourceClient* promisedDataClient() - (void)_web_clearPrintingModeRecursive; @end +#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) + +@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 a normal category and stop doing the forward declaration trick. - (void)_setPrinting:(BOOL)printing minimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustViewSize:(BOOL)adjustViewSize; @end @@ -366,24 +387,6 @@ static CachedResourceClient* promisedDataClient() - (void)_web_setObjectIfNotNil:(id)object forKey:(id)key; @end -// Handles the complete: text command -@interface WebTextCompleteController : NSObject <NSTableViewDelegate, NSTableViewDataSource> { -@private - WebHTMLView *_view; - NSWindow *_popupWindow; - NSTableView *_tableView; - NSArray *_completions; - NSString *_originalString; - int prefixLength; -} -- (id)initWithHTMLView:(WebHTMLView *)view; -- (void)doCompletion; -- (void)endRevertingChange:(BOOL)revertChange moveLeft:(BOOL)goLeft; -- (BOOL)popupWindowIsOpen; -- (BOOL)filterKeyDown:(NSEvent *)event; -- (void)_reflectSelection; -@end - struct WebHTMLViewInterpretKeyEventsParameters { KeyboardEvent* event; BOOL eventWasHandled; @@ -396,7 +399,6 @@ struct WebHTMLViewInterpretKeyEventsParameters { @interface WebHTMLViewPrivate : NSObject { @public BOOL closed; - BOOL needsLayout; BOOL needsToApplyStyles; BOOL ignoringMouseDraggedEvents; BOOL printing; @@ -404,7 +406,6 @@ struct WebHTMLViewInterpretKeyEventsParameters { BOOL observingMouseMovedNotifications; BOOL observingSuperviewNotifications; BOOL observingWindowNotifications; - BOOL resigningFirstResponder; id savedSubviews; BOOL subviewsSetAside; @@ -416,8 +417,10 @@ struct WebHTMLViewInterpretKeyEventsParameters { NSEvent *mouseDownEvent; // Kept after handling the event. BOOL handlingMouseDownEvent; NSEvent *keyDownEvent; // Kept after handling the event. - - NSSize lastLayoutSize; + + // 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; NSPoint lastScrollPosition; @@ -439,7 +442,7 @@ struct WebHTMLViewInterpretKeyEventsParameters { BOOL nextResponderDisabledOnce; #endif - WebTextCompleteController *compController; + WebTextCompletionController *completionController; BOOL transparentBackground; @@ -449,7 +452,6 @@ struct WebHTMLViewInterpretKeyEventsParameters { WebDataSource *dataSource; WebCore::CachedImage* promisedDragTIFFDataSource; - CFRunLoopTimerRef updateFocusedAndActiveStateTimer; CFRunLoopTimerRef updateMouseoverTimer; SEL selectorForDoCommandBySelector; @@ -514,14 +516,13 @@ static NSCellStateValue kit(TriState state) ASSERT(!autoscrollTimer); ASSERT(!autoscrollTriggerEvent); - ASSERT(!updateFocusedAndActiveStateTimer); ASSERT(!updateMouseoverTimer); [mouseDownEvent release]; [keyDownEvent release]; [pluginController release]; [toolTip release]; - [compController release]; + [completionController release]; [dataSource release]; [highlighters release]; if (promisedDragTIFFDataSource) @@ -546,7 +547,7 @@ static NSCellStateValue kit(TriState state) [keyDownEvent release]; [pluginController release]; [toolTip release]; - [compController release]; + [completionController release]; [dataSource release]; [highlighters release]; if (promisedDragTIFFDataSource) @@ -556,7 +557,7 @@ static NSCellStateValue kit(TriState state) keyDownEvent = nil; pluginController = nil; toolTip = nil; - compController = nil; + completionController = nil; dataSource = nil; highlighters = nil; promisedDragTIFFDataSource = 0; @@ -684,6 +685,13 @@ static NSURL* uniqueURLWithRelativePart(NSString *relativePart) subresources:0])) return fragment; + if ([types containsObject:NSRTFDPboardType] && + (fragment = [self _documentFragmentFromPasteboard:pasteboard + forType:NSRTFDPboardType + inContext:context + subresources:0])) + return fragment; + if ([types containsObject:NSRTFPboardType] && (fragment = [self _documentFragmentFromPasteboard:pasteboard forType:NSRTFPboardType @@ -691,16 +699,16 @@ static NSURL* uniqueURLWithRelativePart(NSString *relativePart) subresources:0])) return fragment; - if ([types containsObject:NSRTFDPboardType] && + if ([types containsObject:NSTIFFPboardType] && (fragment = [self _documentFragmentFromPasteboard:pasteboard - forType:NSRTFDPboardType + forType:NSTIFFPboardType inContext:context subresources:0])) return fragment; - if ([types containsObject:NSTIFFPboardType] && + if ([types containsObject:NSPDFPboardType] && (fragment = [self _documentFragmentFromPasteboard:pasteboard - forType:NSTIFFPboardType + forType:NSPDFPboardType inContext:context subresources:0])) return fragment; @@ -746,7 +754,7 @@ static NSURL* uniqueURLWithRelativePart(NSString *relativePart) NSArray *types = [pasteboard types]; if ([types containsObject:NSStringPboardType]) - return [pasteboard stringForType:NSStringPboardType]; + return [[pasteboard stringForType:NSStringPboardType] precomposedStringWithCanonicalMapping]; NSAttributedString *attributedString = nil; NSString *string; @@ -780,20 +788,29 @@ static NSURL* uniqueURLWithRelativePart(NSString *relativePart) - (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText { + WebView *webView = [[self _webView] retain]; + [webView _setInsertionPasteboard:pasteboard]; + DOMRange *range = [self _selectedRange]; - DOMDocumentFragment *fragment = [self _documentFragmentFromPasteboard:pasteboard - inContext:range allowPlainText:allowPlainText]; - WebFrame *frame = [self _frame]; - if (fragment && [self _shouldInsertFragment:fragment replacingDOMRange:[self _selectedRange] givenAction:WebViewInsertActionPasted]) { - [frame _replaceSelectionWithFragment:fragment selectReplacement:NO smartReplace:[self _canSmartReplaceWithPasteboard:pasteboard] matchStyle:NO]; - } + DOMDocumentFragment *fragment = [self _documentFragmentFromPasteboard:pasteboard inContext:range allowPlainText:allowPlainText]; + if (fragment && [self _shouldInsertFragment:fragment replacingDOMRange:range givenAction:WebViewInsertActionPasted]) + [[self _frame] _replaceSelectionWithFragment:fragment selectReplacement:NO smartReplace:[self _canSmartReplaceWithPasteboard:pasteboard] matchStyle:NO]; + + [webView _setInsertionPasteboard:nil]; + [webView release]; } - (void)_pasteAsPlainTextWithPasteboard:(NSPasteboard *)pasteboard { + WebView *webView = [[self _webView] retain]; + [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]; + [webView release]; } - (void)_removeMouseMovedObserverUnconditionally @@ -834,7 +851,6 @@ static NSURL* uniqueURLWithRelativePart(NSString *relativePart) [notificationCenter removeObserver:self name:NSWindowDidBecomeKeyNotification object:nil]; [notificationCenter removeObserver:self name:NSWindowDidResignKeyNotification object:nil]; [notificationCenter removeObserver:self name:NSWindowWillCloseNotification object:window]; - [notificationCenter removeObserver:self name:WKWindowWillOrderOnScreenNotification() object:window]; _private->observingWindowNotifications = false; } @@ -950,15 +966,6 @@ static NSURL* uniqueURLWithRelativePart(NSString *relativePart) _private->mouseDownEvent = event; } -- (void)_cancelUpdateFocusedAndActiveStateTimer -{ - if (_private->updateFocusedAndActiveStateTimer) { - CFRunLoopTimerInvalidate(_private->updateFocusedAndActiveStateTimer); - CFRelease(_private->updateFocusedAndActiveStateTimer); - _private->updateFocusedAndActiveStateTimer = NULL; - } -} - - (void)_cancelUpdateMouseoverTimer { if (_private->updateMouseoverTimer) { @@ -1139,17 +1146,11 @@ static void _updateMouseoverTimerCallback(CFRunLoopTimerRef timer, void *info) - (void)_frameOrBoundsChanged { - if (!NSEqualSizes(_private->lastLayoutSize, [(NSClipView *)[self superview] documentVisibleRect].size)) { - [self setNeedsLayout:YES]; - [self setNeedsDisplay:YES]; - [_private->compController endRevertingChange:NO moveLeft:NO]; - } - NSPoint origin = [[self superview] bounds].origin; if (!NSEqualPoints(_private->lastScrollPosition, origin)) { if (Frame* coreFrame = core([self _frame])) coreFrame->eventHandler()->sendScrollEvent(); - [_private->compController endRevertingChange:NO moveLeft:NO]; + [_private->completionController endRevertingChange:NO moveLeft:NO]; WebView *webView = [self _webView]; [[webView _UIDelegateForwarder] webView:webView didScrollDocumentInFrameView:[self _frameView]]; @@ -1165,6 +1166,10 @@ static void _updateMouseoverTimerCallback(CFRunLoopTimerRef timer, void *info) _updateMouseoverTimerCallback, &context); CFRunLoopAddTimer(CFRunLoopGetCurrent(), _private->updateMouseoverTimer, kCFRunLoopDefaultMode); } + +#if USE(ACCELERATED_COMPOSITING) && defined(BUILDING_ON_LEOPARD) + [self _updateLayerHostingViewPosition]; +#endif } - (void)_setAsideSubviews @@ -1267,12 +1272,20 @@ static void _updateMouseoverTimerCallback(CFRunLoopTimerRef timer, void *info) } else if (wasInPrintingMode) [self _web_clearPrintingModeRecursive]; -#ifdef BUILDING_ON_TIGER - +#ifndef BUILDING_ON_TIGER + // 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 and regions dirtied by the layout which are + // not already dirty. + if ([self _needsLayout]) { + LOG_ERROR("View needs layout. Either -viewWillDraw wasn't called or layout was invalidated during the display operation. Performing layout now."); + [self _web_layoutIfNeededRecursive]; + } +#else // Because Tiger does not have viewWillDraw we need to do layout here. [self _web_layoutIfNeededRecursive]; [_subviews makeObjectsPerformSelector:@selector(_propagateDirtyRectsToOpaqueAncestors)]; - #endif [self _setAsideSubviews]; @@ -1381,10 +1394,13 @@ static void _updateMouseoverTimerCallback(CFRunLoopTimerRef timer, void *info) // 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 cntl-opt-f5 command (move focus to item under cursor) - // and Dictionary's cmd-cntl-D (open dictionary popup for item under cursor). + // 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; + BOOL captureHitsOnSubviews; if (forceNSViewHitTest) captureHitsOnSubviews = NO; @@ -1398,8 +1414,14 @@ static void _updateMouseoverTimerCallback(CFRunLoopTimerRef timer, void *info) || [event type] == NSFlagsChanged); } - if (!captureHitsOnSubviews) - return [super hitTest:point]; + if (!captureHitsOnSubviews) { + NSView* hitView = [super hitTest:point]; +#if USE(ACCELERATED_COMPOSITING) + if (_private && hitView == _private->layerHostingView) + hitView = self; +#endif + return hitView; + } if ([[self superview] mouse:point inRect:[self frame]]) return self; return nil; @@ -1551,18 +1573,18 @@ static void _updateMouseoverTimerCallback(CFRunLoopTimerRef timer, void *info) 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 - // khtml doesn't understand our clipping. + // WebCore doesn't understand our clipping. NSRect visibleRect = [[[[lastHitView _frame] frameView] _scrollView] documentVisibleRect]; float yScroll = visibleRect.origin.y; float xScroll = visibleRect.origin.x; - event = [NSEvent mouseEventWithType:NSMouseMoved - location:NSMakePoint(-1 - xScroll, -1 - yScroll ) - modifierFlags:[[NSApp currentEvent] modifierFlags] - timestamp:[NSDate timeIntervalSinceReferenceDate] - windowNumber:[[view window] windowNumber] - context:[[NSApp currentEvent] context] - eventNumber:0 clickCount:0 pressure:0]; + NSEvent *event = [NSEvent mouseEventWithType:NSMouseMoved + location:NSMakePoint(-1 - xScroll, -1 - yScroll) + modifierFlags:[[NSApp currentEvent] modifierFlags] + timestamp:[NSDate timeIntervalSinceReferenceDate] + windowNumber:[[view window] windowNumber] + context:[[NSApp currentEvent] context] + eventNumber:0 clickCount:0 pressure:0]; if (Frame* lastHitCoreFrame = core([lastHitView _frame])) lastHitCoreFrame->eventHandler()->mouseMoved(event); } @@ -1582,7 +1604,7 @@ static void _updateMouseoverTimerCallback(CFRunLoopTimerRef timer, void *info) { static NSArray *types = nil; if (!types) { - types = [[NSArray alloc] initWithObjects:WebArchivePboardType, NSHTMLPboardType, NSFilenamesPboardType, NSTIFFPboardType, + types = [[NSArray alloc] initWithObjects:WebArchivePboardType, NSHTMLPboardType, NSFilenamesPboardType, NSTIFFPboardType, NSPDFPboardType, #if defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD) NSPICTPboardType, #endif @@ -1736,7 +1758,7 @@ static void _updateMouseoverTimerCallback(CFRunLoopTimerRef timer, void *info) return [[self _webView] smartInsertDeleteEnabled] && [[pasteboard types] containsObject:WebSmartPastePboardType]; } -- (void)_startAutoscrollTimer: (NSEvent *)triggerEvent +- (void)_startAutoscrollTimer:(NSEvent *)triggerEvent { if (_private->autoscrollTimer == nil) { _private->autoscrollTimer = [[NSTimer scheduledTimerWithTimeInterval:AUTOSCROLL_INTERVAL @@ -1910,13 +1932,6 @@ static void _updateMouseoverTimerCallback(CFRunLoopTimerRef timer, void *info) [_private->highlighters removeObjectForKey:type]; } -- (void)_updateFocusedAndActiveState -{ - [self _cancelUpdateFocusedAndActiveStateTimer]; - - [[self _webView] _updateFocusedAndActiveStateForFrame:[self _frame]]; -} - - (void)_writeSelectionToPasteboard:(NSPasteboard *)pasteboard { ASSERT([self _hasSelection]); @@ -1946,7 +1961,6 @@ static void _updateMouseoverTimerCallback(CFRunLoopTimerRef timer, void *info) _private->closed = YES; [self _cancelUpdateMouseoverTimer]; - [self _cancelUpdateFocusedAndActiveStateTimer]; [self _clearLastHitViewIfSelf]; [self _removeMouseMovedObserverUnconditionally]; [self _removeWindowObservers]; @@ -2057,6 +2071,16 @@ static void _updateMouseoverTimerCallback(CFRunLoopTimerRef timer, void *info) [resource release]; return fragment; } + if (pboardType == NSPDFPboardType) { + WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:NSPDFPboardType] + URL:uniqueURLWithRelativePart(@"application.pdf") + MIMEType:@"application/pdf" + textEncodingName:nil + frameName:nil]; + DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithImageResource:resource]; + [resource release]; + return fragment; + } #if defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD) if (pboardType == NSPICTPboardType) { WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:NSPICTPboardType] @@ -2173,23 +2197,14 @@ static void _updateMouseoverTimerCallback(CFRunLoopTimerRef timer, void *info) @end -@interface NSString (WebHTMLViewFileInternal) -- (BOOL)matchesExtensionEquivalent:(NSString *)extension; -@end - -@implementation NSString (WebHTMLViewFileInternal) - -- (BOOL)matchesExtensionEquivalent:(NSString *)extension +static bool matchesExtensionOrEquivalent(NSString *filename, NSString *extension) { - if ([self hasSuffix:extension]) - return YES; - else if ([extension isEqualToString:@"jpeg"] && [self hasSuffix:@"jpg"]) - return YES; - return NO; + NSString *extensionAsSuffix = [@"." stringByAppendingString:extension]; + return [filename _webkit_hasCaseInsensitiveSuffix:extensionAsSuffix] + || ([extension _webkit_isCaseInsensitiveEqualToString:@"jpeg"] + && [filename _webkit_hasCaseInsensitiveSuffix:@".jpg"]); } -@end - #ifdef BUILDING_ON_TIGER // The following is a workaround for @@ -2245,7 +2260,6 @@ static void _updateMouseoverTimerCallback(CFRunLoopTimerRef timer, void *info) _private = [[WebHTMLViewPrivate alloc] init]; _private->pluginController = [[WebPluginController alloc] initWithDocumentView:self]; - _private->needsLayout = YES; return self; } @@ -2637,6 +2651,58 @@ WEBCORE_COMMAND(yankAndSelect) return YES; } #endif + +#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) + if (action == @selector(orderFrontSubstitutionsPanel:)) { + NSMenuItem *menuItem = (NSMenuItem *)item; + if ([menuItem isKindOfClass:[NSMenuItem class]]) { + BOOL panelShowing = [[[NSSpellChecker sharedSpellChecker] substitutionsPanel] isVisible]; + [menuItem setTitle:panelShowing + ? UI_STRING("Hide Substitutions", "menu item title") + : UI_STRING("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] ? NSOnState : NSOffState]; + return [self _canEdit]; + } + if (action == @selector(toggleAutomaticQuoteSubstitution:)) { + NSMenuItem *menuItem = (NSMenuItem *)item; + if ([menuItem isKindOfClass:[NSMenuItem class]]) + [menuItem setState:[self isAutomaticQuoteSubstitutionEnabled] ? NSOnState : NSOffState]; + return [self _canEdit]; + } + if (action == @selector(toggleAutomaticLinkDetection:)) { + NSMenuItem *menuItem = (NSMenuItem *)item; + if ([menuItem isKindOfClass:[NSMenuItem class]]) + [menuItem setState:[self isAutomaticLinkDetectionEnabled] ? NSOnState : NSOffState]; + return [self _canEdit]; + } + if (action == @selector(toggleAutomaticDashSubstitution:)) { + NSMenuItem *menuItem = (NSMenuItem *)item; + if ([menuItem isKindOfClass:[NSMenuItem class]]) + [menuItem setState:[self isAutomaticDashSubstitutionEnabled] ? NSOnState : NSOffState]; + return [self _canEdit]; + } + if (action == @selector(toggleAutomaticTextReplacement:)) { + NSMenuItem *menuItem = (NSMenuItem *)item; + if ([menuItem isKindOfClass:[NSMenuItem class]]) + [menuItem setState:[self isAutomaticTextReplacementEnabled] ? NSOnState : NSOffState]; + return [self _canEdit]; + } + if (action == @selector(toggleAutomaticSpellingCorrection:)) { + NSMenuItem *menuItem = (NSMenuItem *)item; + if ([menuItem isKindOfClass:[NSMenuItem class]]) + [menuItem setState:[self isAutomaticSpellingCorrectionEnabled] ? NSOnState : NSOffState]; + return [self _canEdit]; + } +#endif Editor::Command command = [self coreCommandBySelector:action]; if (command.isSupported()) { @@ -2745,14 +2811,6 @@ WEBCORE_COMMAND(yankAndSelect) - (void)addSuperviewObservers { - // We watch the bounds of our superview, so that we can do a layout when the size - // of the superview changes. This is different from other scrollable things that don't - // need this kind of thing because their layout doesn't change. - - // We need to pay attention to both height and width because our "layout" has to change - // to extend the background the full height of the space and because some elements have - // sizes that are based on the total size of the view. - if (_private->observingSuperviewNotifications) return; @@ -2765,7 +2823,7 @@ WEBCORE_COMMAND(yankAndSelect) [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 size/scroll against the previous layout's size/scroll. We need to + // 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. @@ -2787,7 +2845,6 @@ WEBCORE_COMMAND(yankAndSelect) [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]; - [notificationCenter addObserver:self selector:@selector(windowWillOrderOnScreen:) name:WKWindowWillOrderOnScreenNotification() object:window]; _private->observingWindowNotifications = true; } @@ -2803,12 +2860,6 @@ WEBCORE_COMMAND(yankAndSelect) [self addSuperviewObservers]; } -static void _updateFocusedAndActiveStateTimerCallback(CFRunLoopTimerRef timer, void *info) -{ - WebHTMLView *view = (WebHTMLView *)info; - [view _updateFocusedAndActiveState]; -} - - (void)viewWillMoveToWindow:(NSWindow *)window { // Don't do anything if we aren't initialized. This happens @@ -2823,7 +2874,6 @@ static void _updateFocusedAndActiveStateTimerCallback(CFRunLoopTimerRef timer, v [self _removeWindowObservers]; [self _removeSuperviewObservers]; [self _cancelUpdateMouseoverTimer]; - [self _cancelUpdateFocusedAndActiveStateTimer]; [[self _pluginController] stopAllPlugins]; } @@ -2844,19 +2894,6 @@ static void _updateFocusedAndActiveStateTimerCallback(CFRunLoopTimerRef timer, v [self addSuperviewObservers]; [self addMouseMovedObserver]; - // Schedule this update, rather than making the call right now. - // The reason is that placing the caret in the just-installed view requires - // the HTML/XML document to be available on the WebCore side, but it is not - // at the time this code is running. However, it will be there on the next - // crank of the run loop. Doing this helps to make a blinking caret appear - // in a new, empty window "automatic". - if (!_private->updateFocusedAndActiveStateTimer) { - CFRunLoopTimerContext context = { 0, self, NULL, NULL, NULL }; - _private->updateFocusedAndActiveStateTimer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent(), 0, 0, 0, - _updateFocusedAndActiveStateTimerCallback, &context); - CFRunLoopAddTimer(CFRunLoopGetCurrent(), _private->updateFocusedAndActiveStateTimer, kCFRunLoopDefaultMode); - } - [[self _pluginController] startAllPlugins]; _private->lastScrollPosition = NSZeroPoint; @@ -2921,7 +2958,7 @@ static void _updateFocusedAndActiveStateTimerCallback(CFRunLoopTimerRef timer, v { [self reapplyStyles]; - if (!_private->needsLayout && ![[self _frame] _needsLayout]) + if (![self _needsLayout]) return; #ifdef LOG_TIMES @@ -2931,10 +2968,8 @@ static void _updateFocusedAndActiveStateTimerCallback(CFRunLoopTimerRef timer, v LOG(View, "%@ doing layout", self); Frame* coreFrame = core([self _frame]); - if (!coreFrame) { - _private->needsLayout = NO; + if (!coreFrame) return; - } if (FrameView* coreView = coreFrame->view()) { if (minPageWidth > 0.0) @@ -2945,11 +2980,7 @@ static void _updateFocusedAndActiveStateTimerCallback(CFRunLoopTimerRef timer, v coreView->adjustViewSize(); } } - _private->needsLayout = NO; - if (!_private->printing) - _private->lastLayoutSize = [(NSClipView *)[self superview] documentVisibleRect].size; - #ifdef LOG_TIMES double thisTime = CFAbsoluteTimeGetCurrent() - start; LOG(Timing, "%s layout seconds = %f", [self URL], thisTime); @@ -2964,49 +2995,64 @@ static void _updateFocusedAndActiveStateTimerCallback(CFRunLoopTimerRef timer, v // 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); } - (NSMenu *)menuForEvent:(NSEvent *)event { - [_private->compController endRevertingChange:NO moveLeft:NO]; + // 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->handlingMouseDownEvent = YES; - BOOL handledEvent = NO; - Frame* coreFrame = core([self _frame]); + [_private->completionController endRevertingChange:NO moveLeft:NO]; - if (!coreFrame) { - _private->handlingMouseDownEvent = 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(); - // Match behavior of other browsers by sending an onmousedown event for right clicks. coreFrame->eventHandler()->mouseDown(event); - handledEvent = coreFrame->eventHandler()->sendContextMenuEvent(PlatformMouseEvent(event)); + BOOL handledEvent = coreFrame->eventHandler()->sendContextMenuEvent(event); _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* coreMenu = page->contextMenuController()->contextMenu(); if (!coreMenu) return nil; NSArray* menuItems = coreMenu->platformDescription(); - NSMenu* menu = nil; - if (menuItems && [menuItems count] > 0) { - menu = [[[NSMenu alloc] init] autorelease]; - for (unsigned i = 0; i < [menuItems count]; i++) - [menu addItem:[menuItems objectAtIndex:i]]; - } + if (!menuItems) + return nil; + NSUInteger count = [menuItems count]; + if (!count) + return nil; + + NSMenu* menu = [[[NSMenu alloc] init] autorelease]; + for (NSUInteger i = 0; i < count; i++) + [menu addItem:[menuItems objectAtIndex:i]]; return menu; } @@ -3041,7 +3087,14 @@ static void _updateFocusedAndActiveStateTimerCallback(CFRunLoopTimerRef timer, v - (void)setNeedsLayout: (BOOL)flag { LOG(View, "%@ setNeedsLayout:%@", self, flag ? @"YES" : @"NO"); - _private->needsLayout = flag; + 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()->inPageCache()) + return; + if (FrameView* view = frame->view()) + view->setNeedsLayout(); + } } - (void)setNeedsToApplyStyles: (BOOL)flag @@ -3123,9 +3176,13 @@ static void _updateFocusedAndActiveStateTimerCallback(CFRunLoopTimerRef timer, v #if USE(ACCELERATED_COMPOSITING) if ([[self _webView] _needsOneShotDrawingSynchronization]) { - // Disable screen updates so that drawing into the NSView and - // CALayer updates appear on the screen at the same time. + // Disable screen updates so that any layer changes committed here + // don't show up on the screen before the window flush at the end + // of the current window display. [[self window] disableScreenUpdatesUntilFlush]; + + // Make sure any layer changes that happened as a result of layout + // via -viewWillDraw are committed. [CATransaction flush]; [[self _webView] _setNeedsOneShotDrawingSynchronization:NO]; } @@ -3159,47 +3216,54 @@ static void _updateFocusedAndActiveStateTimerCallback(CFRunLoopTimerRef timer, v - (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 addMouseMovedObserver]; - - if (keyWindow == [self window] || keyWindow == [[self window] attachedSheet]) - [self _updateFocusedAndActiveState]; } - (void)windowDidResignKey:(NSNotification *)notification { + if (!pthread_main_np()) { + [self performSelectorOnMainThread:_cmd withObject:notification waitUntilDone:NO]; + return; + } + NSWindow *formerKeyWindow = [notification object]; if (formerKeyWindow == [self window]) [self removeMouseMovedObserver]; - if (formerKeyWindow == [self window] || formerKeyWindow == [[self window] attachedSheet]) { - [self _updateFocusedAndActiveState]; - [_private->compController endRevertingChange:NO moveLeft:NO]; - } + if (formerKeyWindow == [self window] || formerKeyWindow == [[self window] attachedSheet]) + [_private->completionController endRevertingChange:NO moveLeft:NO]; } - (void)windowWillClose:(NSNotification *)notification { - [_private->compController endRevertingChange:NO moveLeft:NO]; - [[self _pluginController] destroyAllPlugins]; -} + if (!pthread_main_np()) { + [self performSelectorOnMainThread:_cmd withObject:notification waitUntilDone:NO]; + return; + } -- (void)windowWillOrderOnScreen:(NSNotification *)notification -{ - if (![[self _webView] shouldUpdateWhileOffscreen]) - [self setNeedsDisplay:YES]; + [_private->completionController endRevertingChange:NO moveLeft:NO]; + [[self _pluginController] destroyAllPlugins]; } - (void)scrollWheel:(NSEvent *)event { - [self retain]; + // 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]; + Frame* frame = core([self _frame]); if (!frame || !frame->eventHandler()->wheelEvent(event)) [super scrollWheel:event]; - [self release]; } - (BOOL)_isSelectionEvent:(NSEvent *)event @@ -3210,6 +3274,11 @@ static void _updateFocusedAndActiveStateTimerCallback(CFRunLoopTimerRef timer, v - (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; @@ -3234,16 +3303,22 @@ static void _updateFocusedAndActiveStateTimerCallback(CFRunLoopTimerRef timer, v - (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]) + if ([hitHTMLView _isSelectionEvent:event]) { if (Frame* coreFrame = core([hitHTMLView _frame])) { [hitHTMLView _setMouseDownEvent:event]; result = coreFrame->eventHandler()->eventMayStartDrag(event); [hitHTMLView _setMouseDownEvent:nil]; } + } return result; } return [hitView shouldDelayWindowOrderingForEvent:event]; @@ -3251,6 +3326,11 @@ static void _updateFocusedAndActiveStateTimerCallback(CFRunLoopTimerRef timer, v - (void)mouseDown:(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> protector = self; if ([[self inputContext] wantsToHandleMouseEvents] && [[self inputContext] handleMouseEvent:event]) return; @@ -3264,7 +3344,7 @@ static void _updateFocusedAndActiveStateTimerCallback(CFRunLoopTimerRef timer, v if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event]) goto done; - [_private->compController endRevertingChange:NO moveLeft:NO]; + [_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. @@ -3298,15 +3378,23 @@ done: - (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]; + NSInputManager *currentInputManager = [NSInputManager currentInputManager]; if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event]) return; [self retain]; - if (!_private->ignoringMouseDraggedEvents) - if (Frame* coreframe = core([self _frame])) - coreframe->eventHandler()->mouseDragged(event); + if (!_private->ignoringMouseDraggedEvents) { + if (Frame* frame = core([self _frame])) { + if (Page* page = frame->page()) + page->mainFrame()->eventHandler()->mouseDragged(event); + } + } [self release]; } @@ -3315,15 +3403,15 @@ done: { ASSERT(![self _webView] || [self _isTopHTMLView]); - Page *page = core([self _webView]); - + Page* page = core([self _webView]); if (!page) return NSDragOperationNone; - - if (page->dragController()->dragOperation() == DragOperationNone) + + // FIXME: Why do we override the source provided operation here? Why not in DragController::startDrag + if (page->dragController()->sourceDragOperation() == DragOperationNone) return NSDragOperationGeneric | NSDragOperationCopy; - - return (NSDragOperation)page->dragController()->dragOperation(); + + return (NSDragOperation)page->dragController()->sourceDragOperation(); } - (void)draggedImage:(NSImage *)image movedTo:(NSPoint)screenLoc @@ -3388,7 +3476,7 @@ done: wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:data] autorelease]; NSString* filename = [response suggestedFilename]; NSString* trueExtension(tiffResource->image()->filenameExtension()); - if (![filename matchesExtensionEquivalent:trueExtension]) + if (!matchesExtensionOrEquivalent(filename, trueExtension)) filename = [[filename stringByAppendingString:@"."] stringByAppendingString:trueExtension]; [wrapper setPreferredFilename:filename]; } @@ -3431,6 +3519,11 @@ noPromisedData: - (void)mouseUp:(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]; + [self _setMouseDownEvent:nil]; NSInputManager *currentInputManager = [NSInputManager currentInputManager]; @@ -3440,8 +3533,10 @@ noPromisedData: [self retain]; [self _stopAutoscrollTimer]; - if (Frame* coreframe = core([self _frame])) - coreframe->eventHandler()->mouseUp(event); + if (Frame* frame = core([self _frame])) { + if (Page* page = frame->page()) + page->mainFrame()->eventHandler()->mouseUp(event); + } [self _updateMouseoverWithFakeEvent]; [self release]; @@ -3459,29 +3554,50 @@ noPromisedData: return YES; } +// 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) +{ + return coreFrame && !coreFrame->selection()->isNone() && coreFrame->selection()->isContentEditable(); +} + +static BOOL isInPasswordField(Frame* coreFrame) +{ + return coreFrame && coreFrame->selection()->isInPasswordField(); +} + - (BOOL)becomeFirstResponder { NSSelectionDirection direction = NSDirectSelection; if (![[self _webView] _isPerformingProgrammaticFocus]) direction = [[self window] keyViewSelectionDirection]; - [self _updateFocusedAndActiveState]; [self _updateFontPanel]; Frame* frame = core([self _frame]); if (!frame) return YES; - - frame->editor()->setStartNewKillRingSequence(true); - if (direction == NSDirectSelection) - return YES; + BOOL exposeInputContext = isTextInput(frame) && !isInPasswordField(frame); + if (exposeInputContext != _private->exposeInputContext) { + _private->exposeInputContext = exposeInputContext; + [NSApp updateWindows]; + } + + frame->editor()->setStartNewKillRingSequence(true); Page* page = frame->page(); if (!page) return YES; - page->focusController()->setFocusedFrame(frame); + if (![[self _webView] _isPerformingProgrammaticFocus]) + page->focusController()->setFocusedFrame(frame); + + page->focusController()->setFocused(true); + + if (direction == NSDirectSelection) + return YES; + if (Document* document = frame->document()) document->setFocusedNode(0); page->focusController()->setInitialFocus(direction == NSSelectingNext ? FocusDirectionForward : FocusDirectionBackward, @@ -3493,15 +3609,24 @@ noPromisedData: { BOOL resign = [super resignFirstResponder]; if (resign) { - _private->resigningFirstResponder = YES; - [_private->compController endRevertingChange:NO moveLeft:NO]; + [_private->completionController endRevertingChange:NO moveLeft:NO]; + Frame* coreFrame = core([self _frame]); + if (!coreFrame) + return resign; + Page* page = coreFrame->page(); + if (!page) + return resign; if (![self maintainsInactiveSelection]) { [self deselectAll]; if (![[self _webView] _isPerformingProgrammaticFocus]) [self clearFocus]; } - [self _updateFocusedAndActiveState]; - _private->resigningFirstResponder = NO; + + 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; } @@ -3582,8 +3707,10 @@ noPromisedData: [self _setPrinting:YES minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO]; float newBottomFloat = *newBottom; - if (FrameView* view = core([self _frame])->view()) - view->adjustPageHeight(&newBottomFloat, oldTop, oldBottom, bottomLimit); + if (Frame* frame = core([self _frame])) { + if (FrameView* view = frame->view()) + view->adjustPageHeight(&newBottomFloat, oldTop, oldBottom, bottomLimit); + } #ifdef __LP64__ // If the new bottom is equal to the old bottom (when both are treated as floats), we just copy @@ -3781,6 +3908,11 @@ noPromisedData: - (void)keyDown:(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; BOOL eventWasSentToWebCore = (_private->keyDownEvent == event); @@ -3789,15 +3921,15 @@ noPromisedData: [_private->keyDownEvent release]; _private->keyDownEvent = [event retain]; - BOOL completionPopupWasOpen = _private->compController && [_private->compController popupWindowIsOpen]; + 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->compController endRevertingChange:YES moveLeft:NO]; - } else if (!_private->compController || ![_private->compController filterKeyDown:event]) { + [_private->completionController endRevertingChange:YES moveLeft:NO]; + } else if (!_private->completionController || ![_private->completionController filterKeyDown:event]) { // Not consumed by complete: popup window - [_private->compController endRevertingChange:YES moveLeft:NO]; + [_private->completionController endRevertingChange:YES moveLeft:NO]; callSuper = YES; } if (callSuper) @@ -3808,6 +3940,11 @@ noPromisedData: - (void)keyUp:(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); RetainPtr<WebHTMLView> selfProtector = self; @@ -3820,6 +3957,11 @@ noPromisedData: - (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]; + Frame* coreFrame = core([self _frame]); if (coreFrame) coreFrame->eventHandler()->capsLockStateMayHaveChanged(); @@ -4052,6 +4194,11 @@ noPromisedData: - (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]; + if ([self _handleStyleKeyEquivalent:event]) return YES; @@ -4144,6 +4291,8 @@ noPromisedData: 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; @@ -4153,7 +4302,8 @@ noPromisedData: // Find the font the same way the rendering code would later if it encountered this CSS. NSFontTraitMask traits = aIsItalic ? NSFontItalicTrait : 0; - NSFont *foundFont = WebCoreFindFont(aFamilyName, traits, aWeight, aPointSize); + int weight = aIsBold ? STANDARD_BOLD_WEIGHT : STANDARD_WEIGHT; + NSFont *foundFont = [WebFontCache fontWithFamily:aFamilyName traits:traits weight:weight size:aPointSize]; // 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. @@ -4173,8 +4323,9 @@ noPromisedData: else if (aPointSize > soa) [style _setFontSizeDelta:@"1px"]; + // FIXME: Map to the entire range of CSS weight values. if (aWeight == bWeight) - [style setFontWeight:aWeight > MIN_BOLD_WEIGHT ? @"bold" : @"normal"]; + [style setFontWeight:aIsBold ? @"bold" : @"normal"]; if (aIsItalic == bIsItalic) [style setFontStyle:aIsItalic ? @"italic" : @"normal"]; @@ -4408,9 +4559,9 @@ NSStrokeColorAttributeName /* NSColor, default nil: same as foreground co if (![self _canEdit]) return; - if (!_private->compController) - _private->compController = [[WebTextCompleteController alloc] initWithHTMLView:self]; - [_private->compController doCompletion]; + if (!_private->completionController) + _private->completionController = [[WebTextCompletionController alloc] initWithWebView:[self _webView] HTMLView:self]; + [_private->completionController doCompletion]; } - (void)checkSpelling:(id)sender @@ -4630,7 +4781,13 @@ static BOOL writingDirectionKeyBindingsEnabled() // support them via the key bindings mechanism. - (BOOL)_wantsKeyDownForEvent:(NSEvent *)event { - return YES; + 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; } #else @@ -4704,15 +4861,28 @@ static BOOL writingDirectionKeyBindingsEnabled() #endif +- (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 (Frame* frame = core([self _frame])) - if (FrameView* view = frame->view()) - view->updateControlTints(); + if (pthread_main_np()) + [self _updateControlTints]; + else + [self performSelectorOnMainThread:@selector(_updateControlTints) withObject:nil waitUntilDone:NO]; + [super _windowChangedKeyState]; } @@ -4902,6 +5072,121 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point) #endif +#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) + +- (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]; +} + +#endif + - (void)_lookUpInDictionaryFromMenu:(id)sender { // Dictionary API will accept a whitespace-only string and display UI as if it were real text, @@ -4929,7 +5214,6 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point) return; #endif - // We soft link to get the function that displays the dictionary (either pop-up window or app) to avoid the performance // penalty of linking to another framework. This function changed signature as well as framework between Tiger and Leopard, // so the two cases are handled separately. @@ -5002,7 +5286,7 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point) if (const PlatformKeyboardEvent* platformEvent = event->keyEvent()) { NSEvent *macEvent = platformEvent->macEvent(); - if ([macEvent type] == NSKeyDown && [_private->compController filterKeyDown:macEvent]) + if ([macEvent type] == NSKeyDown && [_private->completionController filterKeyDown:macEvent]) return true; if ([macEvent type] == NSFlagsChanged) @@ -5065,9 +5349,7 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point) { ASSERT(!_private->subviewsSetAside); - if ([[self _frame] _needsLayout]) - _private->needsLayout = YES; - if (_private->needsToApplyStyles || _private->needsLayout) + if (_private->needsToApplyStyles || [self _needsLayout]) [self layout]; } @@ -5099,17 +5381,19 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point) [[self _pluginController] destroyAllPlugins]; } -- (BOOL)_isResigningFirstResponder +- (BOOL)_needsLayout { - return _private->resigningFirstResponder; + return [[self _frame] _needsLayout]; } #if USE(ACCELERATED_COMPOSITING) - (void)attachRootLayer:(CALayer*)layer { if (!_private->layerHostingView) { - WebLayerHostingView* hostingView = [[WebLayerHostingView alloc] initWithFrame:[self bounds]]; + NSView* hostingView = [[NSView alloc] initWithFrame:[self bounds]]; +#if !defined(BUILDING_ON_LEOPARD) [hostingView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)]; +#endif [self addSubview:hostingView]; [hostingView release]; // hostingView is owned by being a subview of self @@ -5117,13 +5401,35 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point) [[self _webView] _startedAcceleratedCompositingForFrame:[self _frame]]; } - // Make a container layer, which will get sized/positioned by AppKit and CA + // Make a container layer, which will get sized/positioned by AppKit and CA. CALayer* viewLayer = [CALayer layer]; + +#if defined(BUILDING_ON_LEOPARD) + // Turn off default animations. + NSNull *nullValue = [NSNull null]; + NSDictionary *actions = [NSDictionary dictionaryWithObjectsAndKeys: + nullValue, @"anchorPoint", + nullValue, @"bounds", + nullValue, @"contents", + nullValue, @"contentsRect", + nullValue, @"opacity", + nullValue, @"position", + nullValue, @"sublayerTransform", + nullValue, @"sublayers", + nullValue, @"transform", + nil]; + [viewLayer setStyle:[NSDictionary dictionaryWithObject:actions forKey:@"actions"]]; +#endif + [_private->layerHostingView setLayer:viewLayer]; [_private->layerHostingView setWantsLayer:YES]; // Parent our root layer in the container layer [viewLayer addSublayer:layer]; + +#if defined(BUILDING_ON_LEOPARD) + [self _updateLayerHostingViewPosition]; +#endif } - (void)detachRootLayer @@ -5136,7 +5442,37 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point) [[self _webView] _stoppedAcceleratedCompositingForFrame:[self _frame]]; } } -#endif + +#if defined(BUILDING_ON_LEOPARD) +// This method is necessary on Leopard to work around <rdar://problem/7067892>. +- (void)_updateLayerHostingViewPosition +{ + if (!_private->layerHostingView) + return; + + const CGFloat maxHeight = 4096; + NSRect layerViewFrame = [self bounds]; + + if (layerViewFrame.size.height > maxHeight) { + CGFloat documentHeight = layerViewFrame.size.height; + + // Clamp the size of the view to <= 4096px to avoid the bug. + layerViewFrame.size.height = maxHeight; + NSRect visibleRect = [[self enclosingScrollView] documentVisibleRect]; + + // Place the top of the layer-hosting view at the top of the visibleRect. + CGFloat topOffset = NSMinY(visibleRect); + layerViewFrame.origin.y = topOffset; + + // Compensate for the moved view by adjusting the sublayer transform on the view's layer (using flipped coords). + CGFloat bottomOffset = documentHeight - layerViewFrame.size.height - topOffset; + [[_private->layerHostingView layer] setSublayerTransform:CATransform3DMakeTranslation(0, -bottomOffset, 0)]; + } + + [_private->layerHostingView setFrame:layerViewFrame]; +} +#endif // defined(BUILDING_ON_LEOPARD) +#endif // USE(ACCELERATED_COMPOSITING) @end @@ -5160,22 +5496,14 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point) return validAttributes; } -// 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) -{ - return coreFrame && !coreFrame->selection()->isNone() && coreFrame->selection()->isContentEditable(); -} - -static BOOL isInPasswordField(Frame* coreFrame) +- (NSTextInputContext *)inputContext { - return coreFrame && coreFrame->selection()->isInPasswordField(); + return _private->exposeInputContext ? [super inputContext] : nil; } - (NSAttributedString *)textStorage { - Frame* coreFrame = core([self _frame]); - if (!isTextInput(coreFrame) || isInPasswordField(coreFrame)) { + if (!_private->exposeInputContext) { LOG(TextInput, "textStorage -> nil"); return nil; } @@ -5524,6 +5852,15 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde 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]; + } + if (!coreFrame->editor()->hasComposition()) return; @@ -5542,292 +5879,6 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde @end -/* - This class runs the show for handing the complete: NSTextView operation. It counts on its HTML view - to call endRevertingChange: whenever the current completion needs to be aborted. - - The class is in one of two modes: PopupWindow showing, or not. It is shown when a completion yields - more than one match. If a completion yields one or zero matches, it is not shown, and **there is no - state carried across to the next completion**. - */ - -@implementation WebTextCompleteController - -- (id)initWithHTMLView:(WebHTMLView *)view -{ - self = [super init]; - if (!self) - return nil; - _view = view; - return self; -} - -- (void)dealloc -{ - [_popupWindow release]; - [_completions release]; - [_originalString release]; - - [super dealloc]; -} - -- (void)_insertMatch:(NSString *)match -{ - // FIXME: 3769654 - We should preserve case of string being inserted, even in prefix (but then also be - // able to revert that). Mimic NSText. - WebFrame *frame = [_view _frame]; - NSString *newText = [match substringFromIndex:prefixLength]; - [frame _replaceSelectionWithText:newText selectReplacement:YES smartReplace:NO]; -} - -// mostly lifted from NSTextView_KeyBinding.m -- (void)_buildUI -{ - NSRect scrollFrame = NSMakeRect(0, 0, 100, 100); - NSRect tableFrame = NSZeroRect; - tableFrame.size = [NSScrollView contentSizeForFrameSize:scrollFrame.size hasHorizontalScroller:NO hasVerticalScroller:YES borderType:NSNoBorder]; - // Added cast to work around problem with multiple Foundation initWithIdentifier: methods with different parameter types. - NSTableColumn *column = [(NSTableColumn *)[NSTableColumn alloc] initWithIdentifier:[NSNumber numberWithInt:0]]; - [column setWidth:tableFrame.size.width]; - [column setEditable:NO]; - - _tableView = [[NSTableView alloc] initWithFrame:tableFrame]; - [_tableView setAutoresizingMask:NSViewWidthSizable]; - [_tableView addTableColumn:column]; - [column release]; - [_tableView setGridStyleMask:NSTableViewGridNone]; - [_tableView setCornerView:nil]; - [_tableView setHeaderView:nil]; - [_tableView setColumnAutoresizingStyle:NSTableViewUniformColumnAutoresizingStyle]; - [_tableView setDelegate:self]; - [_tableView setDataSource:self]; - [_tableView setTarget:self]; - [_tableView setDoubleAction:@selector(tableAction:)]; - - NSScrollView *scrollView = [[NSScrollView alloc] initWithFrame:scrollFrame]; - [scrollView setBorderType:NSNoBorder]; - [scrollView setHasVerticalScroller:YES]; - [scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; - [scrollView setDocumentView:_tableView]; - [_tableView release]; - - _popupWindow = [[NSWindow alloc] initWithContentRect:scrollFrame styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; - [_popupWindow setAlphaValue:0.88f]; - [_popupWindow setContentView:scrollView]; - [scrollView release]; - [_popupWindow setHasShadow:YES]; - [_popupWindow setOneShot:YES]; - [_popupWindow _setForceActiveControls:YES]; - [_popupWindow setReleasedWhenClosed:NO]; -} - -// mostly lifted from NSTextView_KeyBinding.m -- (void)_placePopupWindow:(NSPoint)topLeft -{ - int numberToShow = [_completions count]; - if (numberToShow > 20) { - numberToShow = 20; - } - - NSRect windowFrame; - NSPoint wordStart = topLeft; - windowFrame.origin = [[_view window] convertBaseToScreen:[_view convertPoint:wordStart toView:nil]]; - windowFrame.size.height = numberToShow * [_tableView rowHeight] + (numberToShow + 1) * [_tableView intercellSpacing].height; - windowFrame.origin.y -= windowFrame.size.height; - NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont systemFontOfSize:12.0f], NSFontAttributeName, nil]; - float maxWidth = 0.0f; - int maxIndex = -1; - int i; - for (i = 0; i < numberToShow; i++) { - float width = ceilf([[_completions objectAtIndex:i] sizeWithAttributes:attributes].width); - if (width > maxWidth) { - maxWidth = width; - maxIndex = i; - } - } - windowFrame.size.width = 100; - if (maxIndex >= 0) { - maxWidth = ceilf([NSScrollView frameSizeForContentSize:NSMakeSize(maxWidth, 100.0f) hasHorizontalScroller:NO hasVerticalScroller:YES borderType:NSNoBorder].width); - maxWidth = ceilf([NSWindow frameRectForContentRect:NSMakeRect(0.0f, 0.0f, maxWidth, 100.0f) styleMask:NSBorderlessWindowMask].size.width); - maxWidth += 5.0f; - windowFrame.size.width = MAX(maxWidth, windowFrame.size.width); - maxWidth = MIN(400.0f, windowFrame.size.width); - } - [_popupWindow setFrame:windowFrame display:NO]; - - [_tableView reloadData]; - [_tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO]; - [_tableView scrollRowToVisible:0]; - [self _reflectSelection]; - [_popupWindow setLevel:NSPopUpMenuWindowLevel]; - [_popupWindow orderFront:nil]; - [[_view window] addChildWindow:_popupWindow ordered:NSWindowAbove]; -} - -- (void)doCompletion -{ - if (!_popupWindow) { - NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker]; - if (!checker) { - LOG_ERROR("No NSSpellChecker"); - return; - } - - // Get preceeding word stem - WebFrame *frame = [_view _frame]; - DOMRange *selection = kit(core(frame)->selection()->toNormalizedRange().get()); - DOMRange *wholeWord = [frame _rangeByAlteringCurrentSelection:SelectionController::EXTEND - direction:SelectionController::BACKWARD granularity:WordGranularity]; - DOMRange *prefix = [wholeWord cloneRange]; - [prefix setEnd:[selection startContainer] offset:[selection startOffset]]; - - // Reject some NOP cases - if ([prefix collapsed]) { - NSBeep(); - return; - } - NSString *prefixStr = [frame _stringForRange:prefix]; - NSString *trimmedPrefix = [prefixStr stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - if ([trimmedPrefix length] == 0) { - NSBeep(); - return; - } - prefixLength = [prefixStr length]; - - // Lookup matches - [_completions release]; - _completions = [checker completionsForPartialWordRange:NSMakeRange(0, [prefixStr length]) inString:prefixStr language:nil inSpellDocumentWithTag:[[_view _webView] spellCheckerDocumentTag]]; - [_completions retain]; - - if (!_completions || [_completions count] == 0) { - NSBeep(); - } else if ([_completions count] == 1) { - [self _insertMatch:[_completions objectAtIndex:0]]; - } else { - ASSERT(!_originalString); // this should only be set IFF we have a popup window - _originalString = [[frame _stringForRange:selection] retain]; - [self _buildUI]; - NSRect wordRect = [frame _caretRectAtNode:[wholeWord startContainer] offset:[wholeWord startOffset] affinity:NSSelectionAffinityDownstream]; - // +1 to be under the word, not the caret - // FIXME - 3769652 - Wrong positioning for right to left languages. We should line up the upper - // right corner with the caret instead of upper left, and the +1 would be a -1. - NSPoint wordLowerLeft = { NSMinX(wordRect)+1, NSMaxY(wordRect) }; - [self _placePopupWindow:wordLowerLeft]; - } - } else { - [self endRevertingChange:YES moveLeft:NO]; - } -} - -- (void)endRevertingChange:(BOOL)revertChange moveLeft:(BOOL)goLeft -{ - if (_popupWindow) { - // tear down UI - [[_view window] removeChildWindow:_popupWindow]; - [_popupWindow orderOut:self]; - // Must autorelease because event tracking code may be on the stack touching UI - [_popupWindow autorelease]; - _popupWindow = nil; - - if (revertChange) { - WebFrame *frame = [_view _frame]; - [frame _replaceSelectionWithText:_originalString selectReplacement:YES smartReplace:NO]; - } else if ([_view _hasSelection]) { - if (goLeft) - [_view moveBackward:nil]; - else - [_view moveForward:nil]; - } - [_originalString release]; - _originalString = nil; - } - // else there is no state to abort if the window was not up -} - -- (BOOL)popupWindowIsOpen -{ - return _popupWindow != nil; -} - -// WebHTMLView gives us a crack at key events it sees. Return whether we consumed the event. -// The features for the various keys mimic NSTextView. -- (BOOL)filterKeyDown:(NSEvent *)event -{ - if (!_popupWindow) - return NO; - NSString *string = [event charactersIgnoringModifiers]; - if (![string length]) - return NO; - unichar c = [string characterAtIndex:0]; - if (c == NSUpArrowFunctionKey) { - int selectedRow = [_tableView selectedRow]; - if (0 < selectedRow) { - [_tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:selectedRow - 1] byExtendingSelection:NO]; - [_tableView scrollRowToVisible:selectedRow - 1]; - } - return YES; - } - if (c == NSDownArrowFunctionKey) { - int selectedRow = [_tableView selectedRow]; - if (selectedRow < (int)[_completions count] - 1) { - [_tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:selectedRow + 1] byExtendingSelection:NO]; - [_tableView scrollRowToVisible:selectedRow + 1]; - } - return YES; - } - if (c == NSRightArrowFunctionKey || c == '\n' || c == '\r' || c == '\t') { - // FIXME: What about backtab? - [self endRevertingChange:NO moveLeft:NO]; - return YES; - } - if (c == NSLeftArrowFunctionKey) { - [self endRevertingChange:NO moveLeft:YES]; - return YES; - } - if (c == 0x1B || c == NSF5FunctionKey) { - // FIXME: F5? - [self endRevertingChange:YES moveLeft:NO]; - return YES; - } - if (c == ' ' || c >= 0x21 && c <= 0x2F || c >= 0x3A && c <= 0x40 || c >= 0x5B && c <= 0x60 || c >= 0x7B && c <= 0x7D) { - // FIXME: Is the above list of keys really definitive? - // Originally this code called ispunct; aren't there other punctuation keys on international keyboards? - [self endRevertingChange:NO moveLeft:NO]; - return NO; // let the char get inserted - } - return NO; -} - -- (void)_reflectSelection -{ - int selectedRow = [_tableView selectedRow]; - ASSERT(selectedRow >= 0 && selectedRow < (int)[_completions count]); - [self _insertMatch:[_completions objectAtIndex:selectedRow]]; -} - -- (void)tableAction:(id)sender -{ - [self _reflectSelection]; - [self endRevertingChange:NO moveLeft:NO]; -} - -- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView -{ - return [_completions count]; -} - -- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row -{ - return [_completions objectAtIndex:row]; -} - -- (void)tableViewSelectionDidChange:(NSNotification *)notification -{ - [self _reflectSelection]; -} - -@end - @implementation WebHTMLView (WebDocumentPrivateProtocols) - (NSRect)selectionRect @@ -6082,4 +6133,10 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde _receivedUnhandledCommand = YES; } +- (BOOL)tryToPerform:(SEL)action with:(id)object +{ + _receivedUnhandledCommand = YES; + return YES; +} + @end |