diff options
Diffstat (limited to 'Source/WebKit/mac/WebView/WebHTMLView.mm')
-rw-r--r-- | Source/WebKit/mac/WebView/WebHTMLView.mm | 325 |
1 files changed, 195 insertions, 130 deletions
diff --git a/Source/WebKit/mac/WebView/WebHTMLView.mm b/Source/WebKit/mac/WebView/WebHTMLView.mm index 00f65bd..e611e2b 100644 --- a/Source/WebKit/mac/WebView/WebHTMLView.mm +++ b/Source/WebKit/mac/WebView/WebHTMLView.mm @@ -383,6 +383,7 @@ static CachedResourceClient* promisedDataClient() - (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)_removeMouseMovedObserverUnconditionally; - (void)_removeSuperviewObservers; - (void)_removeWindowObservers; @@ -461,11 +462,9 @@ static CachedResourceClient* promisedDataClient() struct WebHTMLViewInterpretKeyEventsParameters { KeyboardEvent* event; - BOOL eventWasHandled; - BOOL shouldSaveCommand; - // The Input Method may consume an event and not tell us, in - // which case we should not bubble the event up the DOM - BOOL consumedByIM; + bool eventInterpretationHadSideEffects; + bool shouldSaveCommands; + bool consumedByIM; }; @interface WebHTMLViewPrivate : NSObject { @@ -522,7 +521,6 @@ struct WebHTMLViewInterpretKeyEventsParameters { BOOL transparentBackground; WebHTMLViewInterpretKeyEventsParameters* interpretKeyEventsParameters; - BOOL receivedNOOP; WebDataSource *dataSource; WebCore::CachedImage* promisedDragTIFFDataSource; @@ -914,6 +912,32 @@ static NSURL* uniqueURLWithRelativePart(NSString *relativePart) [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]; +} + +// This method is needed to support Mac OS X services. +- (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pasteboard +{ + Frame* coreFrame = core([self _frame]); + if (!coreFrame) + return NO; + if (coreFrame->selection()->isContentRichlyEditable()) + [self _pasteWithPasteboard:pasteboard allowPlainText:YES]; + else + [self _pasteAsPlainTextWithPasteboard:pasteboard]; + return YES; +} + - (void)_removeMouseMovedObserverUnconditionally { if (!_private || !_private->observingMouseMovedNotifications) @@ -1169,21 +1193,7 @@ static NSURL* uniqueURLWithRelativePart(NSString *relativePart) + (NSArray *)unsupportedTextMIMETypes { - return [NSArray arrayWithObjects: - @"text/calendar", // iCal - @"text/x-calendar", - @"text/x-vcalendar", - @"text/vcalendar", - @"text/vcard", // vCard - @"text/x-vcard", - @"text/directory", - @"text/ldif", // Netscape Address Book - @"text/qif", // Quicken - @"text/x-qif", - @"text/x-csv", // CSV (for Address Book and Microsoft Outlook) - @"text/x-vcf", // vCard type used in Sun affinity app - @"text/rtf", // Rich Text Format - nil]; + return [WebHTMLRepresentation unsupportedTextMIMETypes]; } + (void)_postFlagsChangedEvent:(NSEvent *)flagsChangedEvent @@ -1985,10 +1995,6 @@ static void _updateMouseoverTimerCallback(CFRunLoopTimerRef timer, void *info) [self removeAllToolTips]; [_private clear]; - - Page* page = core([self _webView]); - if (page) - page->dragController()->setDraggingImageURL(KURL()); } - (BOOL)_hasHTMLDocument @@ -2608,6 +2614,7 @@ WEBCORE_COMMAND(yankAndSelect) [NSPasteboard _web_setFindPasteboardString:[self selectedString] withOwner:self]; } +// This method is needed to support Mac OS X services. - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pasteboard types:(NSArray *)types { [pasteboard declareTypes:types owner:[self _topHTMLView]]; @@ -3036,7 +3043,12 @@ WEBCORE_COMMAND(yankAndSelect) // We may have created the layer hosting view while outside the window. Update the scale factor // now that we have a window to get it from. if (_private->layerHostingView) { - CGFloat scaleFactor = [[self window] userSpaceScaleFactor]; + CGFloat scaleFactor; +#if !defined(BUILDING_ON_SNOW_LEOPARD) + scaleFactor = [[self window] backingScaleFactor]; +#else + scaleFactor = [[self window] userSpaceScaleFactor]; +#endif [[_private->layerHostingView layer] setTransform:CATransform3DMakeScale(scaleFactor, scaleFactor, 1)]; } #endif @@ -5416,59 +5428,96 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point) [self _updateMouseoverWithFakeEvent]; } -- (BOOL)_interceptEditingKeyEvent:(KeyboardEvent*)event shouldSaveCommand:(BOOL)shouldSave +- (void)_executeSavedEditingCommands +{ + WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters; + if (!parameters || parameters->event->keypressCommands().isEmpty()) + 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; + + const Vector<KeypressCommand>& commands = parameters->event->keypressCommands(); + + for (size_t i = 0; i < commands.size(); ++i) { + if (commands[i].commandName == "insertText:") + [self insertText:commands[i].text]; + else + [self doCommandBySelector:NSSelectorFromString(commands[i].commandName)]; + } + parameters->event->keypressCommands().clear(); + parameters->shouldSaveCommands = wasSavingCommands; +} + +- (BOOL)_interpretKeyEvent:(KeyboardEvent*)event savingCommands:(BOOL)savingCommands { - // Ask AppKit to process the key event -- it will call back with either insertText or doCommandBySelector. + ASSERT(core([self _frame]) == event->target()->toNode()->document()->frame()); + ASSERT(!savingCommands || event->keypressCommands().isEmpty()); // Save commands once for each event. + WebHTMLViewInterpretKeyEventsParameters parameters; - parameters.eventWasHandled = false; - parameters.shouldSaveCommand = shouldSave; + parameters.eventInterpretationHadSideEffects = false; + parameters.shouldSaveCommands = savingCommands; // 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 = !event->metaKey() && shouldSave; + parameters.consumedByIM = savingCommands && !event->metaKey(); - if (const PlatformKeyboardEvent* platformEvent = event->keyEvent()) { - NSEvent *macEvent = platformEvent->macEvent(); - if ([macEvent type] == NSKeyDown && [_private->completionController filterKeyDown:macEvent]) - return true; - - if ([macEvent type] == NSFlagsChanged) - return false; - - parameters.event = event; - _private->interpretKeyEventsParameters = ¶meters; - _private->receivedNOOP = NO; - const Vector<KeypressCommand>& commands = event->keypressCommands(); - bool hasKeypressCommand = !commands.isEmpty(); - - // FIXME: interpretKeyEvents doesn't match application key equivalents (such as Cmd+A), - // and sends noop: for those. As a result, we don't handle those from within WebCore, - // but send a full sequence of DOM events, including an unneeded keypress. - if (parameters.shouldSaveCommand || !hasKeypressCommand) - [self interpretKeyEvents:[NSArray arrayWithObject:macEvent]]; - else { - size_t size = commands.size(); - // Are there commands that would 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 < size; ++i) { - if ([self coreCommandBySelector:NSSelectorFromString(commands[i].commandName)].isTextInsertion()) - haveTextInsertionCommands = true; - } - if (!haveTextInsertionCommands || platformEvent->type() == PlatformKeyboardEvent::Char) { - for (size_t i = 0; i < size; ++i) { - if (commands[i].commandName == "insertText:") - [self insertText:commands[i].text]; - else - [self doCommandBySelector:NSSelectorFromString(commands[i].commandName)]; - } - } + const PlatformKeyboardEvent* platformEvent = event->keyEvent(); + if (!platformEvent) + return NO; + + NSEvent *macEvent = platformEvent->macEvent(); + if ([macEvent type] == NSKeyDown && [_private->completionController filterKeyDown:macEvent]) + return YES; + + if ([macEvent type] == NSFlagsChanged) + 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; } - _private->interpretKeyEventsParameters = 0; + // 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() == PlatformKeyboardEvent::Char) + [self _executeSavedEditingCommands]; } - return (!_private->receivedNOOP && parameters.eventWasHandled) || parameters.consumedByIM; + _private->interpretKeyEventsParameters = 0; + + // 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; } - (WebCore::CachedImage*)promisedDragTIFFDataSource @@ -5551,7 +5600,20 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point) #if !defined(BUILDING_ON_LEOPARD) // If we aren't in the window yet, we'll use the screen's scale factor now, and reset the scale // via -viewDidMoveToWindow. - CGFloat scaleFactor = [self window] ? [[self window] userSpaceScaleFactor] : [[NSScreen mainScreen] userSpaceScaleFactor]; + NSWindow *window = [self window]; + CGFloat scaleFactor; +#if !defined(BUILDING_ON_SNOW_LEOPARD) + if (window) + scaleFactor = [window backingScaleFactor]; + else + scaleFactor = [[NSScreen mainScreen] backingScaleFactor]; +#else + if (window) + scaleFactor = [window userSpaceScaleFactor]; + else + scaleFactor = [[NSScreen mainScreen] userSpaceScaleFactor]; +#endif + [viewLayer setTransform:CATransform3DMakeScale(scaleFactor, scaleFactor, 1)]; #endif @@ -5684,6 +5746,8 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point) - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint { + [self _executeSavedEditingCommands]; + NSWindow *window = [self window]; WebFrame *frame = [self _frame]; @@ -5703,7 +5767,9 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point) } - (NSRect)firstRectForCharacterRange:(NSRange)theRange -{ +{ + [self _executeSavedEditingCommands]; + WebFrame *frame = [self _frame]; // Just to match NSTextView's behavior. Regression tests cannot detect this; @@ -5734,6 +5800,8 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point) - (NSRange)selectedRange { + [self _executeSavedEditingCommands]; + if (!isTextInput(core([self _frame]))) { LOG(TextInput, "selectedRange -> (NSNotFound, 0)"); return NSMakeRange(NSNotFound, 0); @@ -5746,6 +5814,8 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point) - (NSRange)markedRange { + [self _executeSavedEditingCommands]; + WebFrame *webFrame = [self _frame]; Frame* coreFrame = core(webFrame); if (!coreFrame) @@ -5758,6 +5828,8 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point) - (NSAttributedString *)attributedSubstringFromRange:(NSRange)nsRange { + [self _executeSavedEditingCommands]; + WebFrame *frame = [self _frame]; Frame* coreFrame = core(frame); if (!isTextInput(coreFrame) || isInPasswordField(coreFrame)) { @@ -5799,6 +5871,8 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point) - (BOOL)hasMarkedText { + [self _executeSavedEditingCommands]; + Frame* coreFrame = core([self _frame]); BOOL result = coreFrame && coreFrame->editor()->hasComposition(); LOG(TextInput, "hasMarkedText -> %u", result); @@ -5807,6 +5881,8 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point) - (void)unmarkText { + [self _executeSavedEditingCommands]; + LOG(TextInput, "unmarkText"); // Use pointer to get parameters passed to us by the caller of interpretKeyEvents. @@ -5814,8 +5890,8 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point) _private->interpretKeyEventsParameters = 0; if (parameters) { - parameters->eventWasHandled = YES; - parameters->consumedByIM = NO; + parameters->eventInterpretationHadSideEffects = true; + parameters->consumedByIM = false; } if (Frame* coreFrame = core([self _frame])) @@ -5844,6 +5920,8 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange { + [self _executeSavedEditingCommands]; + BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; // Otherwise, NSString LOG(TextInput, "setMarkedText:\"%@\" selectedRange:(%u, %u)", isAttributedString ? [string string] : string, newSelRange.location, newSelRange.length); @@ -5853,8 +5931,8 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde _private->interpretKeyEventsParameters = 0; if (parameters) { - parameters->eventWasHandled = YES; - parameters->consumedByIM = NO; + parameters->eventInterpretationHadSideEffects = true; + parameters->consumedByIM = false; } Frame* coreFrame = core([self _frame]); @@ -5869,7 +5947,7 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde if (isAttributedString) { unsigned markedTextLength = [(NSString *)string length]; - NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:NULL inRange:NSMakeRange(0, markedTextLength)]; + NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:0 inRange:NSMakeRange(0, markedTextLength)]; 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 the text that should be replaced with the new string. @@ -5891,17 +5969,16 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde // The same call to interpretKeyEvents can do more than one command. WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters; if (parameters) - parameters->consumedByIM = NO; - - if (selector == @selector(noop:)) { - _private->receivedNOOP = YES; - return; - } + parameters->consumedByIM = false; KeyboardEvent* event = parameters ? parameters->event : 0; - bool shouldSaveCommand = parameters && parameters->shouldSaveCommand; + 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) + 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. @@ -5933,10 +6010,8 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde } if (parameters) - parameters->eventWasHandled = eventWasHandled; + parameters->eventInterpretationHadSideEffects |= eventWasHandled; - // Restore the parameters so that other calls to doCommandBySelector: see them, - // and other commands can participate in setting the "eventWasHandled" flag. _private->interpretKeyEventsParameters = parameters; } } @@ -5948,9 +6023,8 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde LOG(TextInput, "insertText:\"%@\"", isAttributedString ? [string string] : string); WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters; - _private->interpretKeyEventsParameters = 0; if (parameters) - parameters->consumedByIM = NO; + parameters->consumedByIM = false; // We don't support inserting an attributed string but input methods don't appear to require this. RefPtr<Frame> coreFrame = core([self _frame]); @@ -5960,57 +6034,48 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde text = [string string]; // We deal with the NSTextInputReplacementRangeAttributeName attribute from NSAttributedString here // simply because it is used by at least one Input Method -- it corresonds to the kEventParamTextInputSendReplaceRange - // event in TSM. This behaviour matches that of -[WebHTMLView setMarkedText:selectedRange:] when it receives an + // event in TSM. This behavior matches that of -[WebHTMLView setMarkedText:selectedRange:] when it receives an // NSAttributedString - NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:NULL inRange:NSMakeRange(0, [text length])]; + NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:0 inRange:NSMakeRange(0, [text length])]; LOG(TextInput, " ReplacementRange: %@", rangeString); if (rangeString) { [[self _frame] _selectNSRange:NSRangeFromString(rangeString)]; - isFromInputMethod = YES; + isFromInputMethod = true; } } else text = string; - bool eventHandled = false; - if ([text length]) { - KeyboardEvent* event = parameters ? parameters->event : 0; - - // insertText can be called from an input method or from normal key event processing - // If its from normal key event processing, we may need to save the action to perform it later. - // If its from an input method, then we should go ahead and 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 - bool shouldSaveCommand = parameters && parameters->shouldSaveCommand; - if (event && shouldSaveCommand && !isFromInputMethod) { - event->keypressCommands().append(KeypressCommand("insertText:", text)); - _private->interpretKeyEventsParameters = parameters; - return; - } - - String eventText = text; - eventText.replace(NSBackTabCharacter, NSTabCharacter); // same thing is done in KeyEventMac.mm in WebCore - if (coreFrame && coreFrame->editor()->canEdit()) { - if (!coreFrame->editor()->hasComposition()) - eventHandled = coreFrame->editor()->insertText(eventText, event); - else { - eventHandled = true; - coreFrame->editor()->confirmComposition(eventText); - } - } - } - - if (!parameters) + 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 go ahead and 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 (isFromInputMethod) { - // Allow doCommandBySelector: to be called after insertText: by resetting interpretKeyEventsParameters - _private->interpretKeyEventsParameters = parameters; - parameters->consumedByIM = YES; + } + + if (!coreFrame || !coreFrame->editor()->canEdit()) return; + + 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. + eventHandled = coreFrame->editor()->insertText(eventText, event); + } else { + eventHandled = true; + coreFrame->editor()->confirmComposition(eventText); } - parameters->eventWasHandled = eventHandled; + if (parameters) + parameters->eventInterpretationHadSideEffects |= eventHandled; } - (void)_updateSelectionForInputManager |