<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[191791] trunk/Source/WebKit2</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/191791">191791</a></dd>
<dt>Author</dt> <dd>timothy_horton@apple.com</dd>
<dt>Date</dt> <dd>2015-10-30 10:02:36 -0700 (Fri, 30 Oct 2015)</dd>
</dl>
<h3>Log Message</h3>
<pre>WKView being inside WKWebView leads to weird API issues
https://bugs.webkit.org/show_bug.cgi?id=150174
Reviewed by Darin Adler.
* UIProcess/API/mac/WKView.mm:
(-[WKView doCommandBySelector:]):
(-[WKView insertText:]):
(-[WKView insertText:replacementRange:]):
(-[WKView inputContext]):
(-[WKView performKeyEquivalent:]):
(-[WKView keyUp:]):
(-[WKView keyDown:]):
(-[WKView flagsChanged:]):
(-[WKView setMarkedText:selectedRange:replacementRange:]):
(-[WKView unmarkText]):
(-[WKView selectedRange]):
(-[WKView hasMarkedText]):
(-[WKView markedRange]):
(-[WKView attributedSubstringForProposedRange:actualRange:]):
(-[WKView characterIndexForPoint:]):
(-[WKView firstRectForCharacterRange:actualRange:]):
(-[WKView selectedRangeWithCompletionHandler:]):
(-[WKView markedRangeWithCompletionHandler:]):
(-[WKView hasMarkedTextWithCompletionHandler:]):
(-[WKView attributedSubstringForProposedRange:completionHandler:]):
(-[WKView firstRectForCharacterRange:completionHandler:]):
(-[WKView characterIndexForPoint:completionHandler:]):
(-[WKView _superPerformKeyEquivalent:]):
(-[WKView _superKeyDown:]):
(extractUnderlines): Deleted.
(-[WKView _collectKeyboardLayoutCommandsForEvent:to:]): Deleted.
(-[WKView _interpretKeyEvent:completionHandler:]): Deleted.
(-[WKView NO_RETURN_DUE_TO_ASSERT]): Deleted.
(-[WKView _interpretKeyEvent:savingCommandsTo:]): Deleted.
(-[WKView _executeSavedKeypressCommands]): Deleted.
(-[WKView _doneWithKeyEvent:eventWasHandled:]): Deleted.
* UIProcess/API/mac/WKViewInternal.h:
* UIProcess/Cocoa/WebViewImpl.h:
* UIProcess/Cocoa/WebViewImpl.mm:
(WebKit::WebViewImpl::doneWithKeyEvent):
(WebKit::extractUnderlines):
(WebKit::WebViewImpl::collectKeyboardLayoutCommandsForEvent):
(WebKit::WebViewImpl::interpretKeyEvent):
(WebKit::WebViewImpl::doCommandBySelector):
(WebKit::WebViewImpl::insertText):
(WebKit::WebViewImpl::selectedRangeWithCompletionHandler):
(WebKit::WebViewImpl::markedRangeWithCompletionHandler):
(WebKit::WebViewImpl::hasMarkedTextWithCompletionHandler):
(WebKit::WebViewImpl::attributedSubstringForProposedRange):
(WebKit::WebViewImpl::firstRectForCharacterRange):
(WebKit::WebViewImpl::characterIndexForPoint):
(WebKit::WebViewImpl::inputContext):
(WebKit::WebViewImpl::unmarkText):
(WebKit::WebViewImpl::setMarkedText):
(WebKit::WebViewImpl::selectedRange):
(WebKit::WebViewImpl::hasMarkedText):
(WebKit::WebViewImpl::markedRange):
(WebKit::WebViewImpl::performKeyEquivalent):
(WebKit::WebViewImpl::keyUp):
(WebKit::WebViewImpl::keyDown):
(WebKit::WebViewImpl::flagsChanged):
(WebKit::WebViewImpl::executeSavedKeypressCommands):
* UIProcess/mac/PageClientImpl.mm:
(WebKit::PageClientImpl::doneWithKeyEvent):
Move NSTextInputClient implementation.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkSourceWebKit2ChangeLog">trunk/Source/WebKit2/ChangeLog</a></li>
<li><a href="#trunkSourceWebKit2UIProcessAPImacWKViewmm">trunk/Source/WebKit2/UIProcess/API/mac/WKView.mm</a></li>
<li><a href="#trunkSourceWebKit2UIProcessAPImacWKViewInternalh">trunk/Source/WebKit2/UIProcess/API/mac/WKViewInternal.h</a></li>
<li><a href="#trunkSourceWebKit2UIProcessCocoaWebViewImplh">trunk/Source/WebKit2/UIProcess/Cocoa/WebViewImpl.h</a></li>
<li><a href="#trunkSourceWebKit2UIProcessCocoaWebViewImplmm">trunk/Source/WebKit2/UIProcess/Cocoa/WebViewImpl.mm</a></li>
<li><a href="#trunkSourceWebKit2UIProcessmacPageClientImplmm">trunk/Source/WebKit2/UIProcess/mac/PageClientImpl.mm</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkSourceWebKit2ChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebKit2/ChangeLog (191790 => 191791)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebKit2/ChangeLog        2015-10-30 16:51:19 UTC (rev 191790)
+++ trunk/Source/WebKit2/ChangeLog        2015-10-30 17:02:36 UTC (rev 191791)
</span><span class="lines">@@ -1,3 +1,72 @@
</span><ins>+2015-10-30 Tim Horton <timothy_horton@apple.com>
+
+ WKView being inside WKWebView leads to weird API issues
+ https://bugs.webkit.org/show_bug.cgi?id=150174
+
+ Reviewed by Darin Adler.
+
+ * UIProcess/API/mac/WKView.mm:
+ (-[WKView doCommandBySelector:]):
+ (-[WKView insertText:]):
+ (-[WKView insertText:replacementRange:]):
+ (-[WKView inputContext]):
+ (-[WKView performKeyEquivalent:]):
+ (-[WKView keyUp:]):
+ (-[WKView keyDown:]):
+ (-[WKView flagsChanged:]):
+ (-[WKView setMarkedText:selectedRange:replacementRange:]):
+ (-[WKView unmarkText]):
+ (-[WKView selectedRange]):
+ (-[WKView hasMarkedText]):
+ (-[WKView markedRange]):
+ (-[WKView attributedSubstringForProposedRange:actualRange:]):
+ (-[WKView characterIndexForPoint:]):
+ (-[WKView firstRectForCharacterRange:actualRange:]):
+ (-[WKView selectedRangeWithCompletionHandler:]):
+ (-[WKView markedRangeWithCompletionHandler:]):
+ (-[WKView hasMarkedTextWithCompletionHandler:]):
+ (-[WKView attributedSubstringForProposedRange:completionHandler:]):
+ (-[WKView firstRectForCharacterRange:completionHandler:]):
+ (-[WKView characterIndexForPoint:completionHandler:]):
+ (-[WKView _superPerformKeyEquivalent:]):
+ (-[WKView _superKeyDown:]):
+ (extractUnderlines): Deleted.
+ (-[WKView _collectKeyboardLayoutCommandsForEvent:to:]): Deleted.
+ (-[WKView _interpretKeyEvent:completionHandler:]): Deleted.
+ (-[WKView NO_RETURN_DUE_TO_ASSERT]): Deleted.
+ (-[WKView _interpretKeyEvent:savingCommandsTo:]): Deleted.
+ (-[WKView _executeSavedKeypressCommands]): Deleted.
+ (-[WKView _doneWithKeyEvent:eventWasHandled:]): Deleted.
+ * UIProcess/API/mac/WKViewInternal.h:
+ * UIProcess/Cocoa/WebViewImpl.h:
+ * UIProcess/Cocoa/WebViewImpl.mm:
+ (WebKit::WebViewImpl::doneWithKeyEvent):
+ (WebKit::extractUnderlines):
+ (WebKit::WebViewImpl::collectKeyboardLayoutCommandsForEvent):
+ (WebKit::WebViewImpl::interpretKeyEvent):
+ (WebKit::WebViewImpl::doCommandBySelector):
+ (WebKit::WebViewImpl::insertText):
+ (WebKit::WebViewImpl::selectedRangeWithCompletionHandler):
+ (WebKit::WebViewImpl::markedRangeWithCompletionHandler):
+ (WebKit::WebViewImpl::hasMarkedTextWithCompletionHandler):
+ (WebKit::WebViewImpl::attributedSubstringForProposedRange):
+ (WebKit::WebViewImpl::firstRectForCharacterRange):
+ (WebKit::WebViewImpl::characterIndexForPoint):
+ (WebKit::WebViewImpl::inputContext):
+ (WebKit::WebViewImpl::unmarkText):
+ (WebKit::WebViewImpl::setMarkedText):
+ (WebKit::WebViewImpl::selectedRange):
+ (WebKit::WebViewImpl::hasMarkedText):
+ (WebKit::WebViewImpl::markedRange):
+ (WebKit::WebViewImpl::performKeyEquivalent):
+ (WebKit::WebViewImpl::keyUp):
+ (WebKit::WebViewImpl::keyDown):
+ (WebKit::WebViewImpl::flagsChanged):
+ (WebKit::WebViewImpl::executeSavedKeypressCommands):
+ * UIProcess/mac/PageClientImpl.mm:
+ (WebKit::PageClientImpl::doneWithKeyEvent):
+ Move NSTextInputClient implementation.
+
</ins><span class="cx"> 2015-10-30 Carlos Garcia Campos <cgarcia@igalia.com>
</span><span class="cx">
</span><span class="cx"> [GTK] Move the socket polling off the WorkQueue
</span></span></pre></div>
<a id="trunkSourceWebKit2UIProcessAPImacWKViewmm"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebKit2/UIProcess/API/mac/WKView.mm (191790 => 191791)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebKit2/UIProcess/API/mac/WKView.mm        2015-10-30 16:51:19 UTC (rev 191790)
+++ trunk/Source/WebKit2/UIProcess/API/mac/WKView.mm        2015-10-30 17:02:36 UTC (rev 191791)
</span><span class="lines">@@ -110,41 +110,14 @@
</span><span class="cx"> #import "WKBrowsingContextGroupPrivate.h"
</span><span class="cx"> #import "WKProcessGroupPrivate.h"
</span><span class="cx">
</span><del>-#if USE(ASYNC_NSTEXTINPUTCLIENT)
-@interface NSTextInputContext (WKNSTextInputContextDetails)
-- (void)handleEvent:(NSEvent *)theEvent completionHandler:(void(^)(BOOL handled))completionHandler;
-- (void)handleEventByInputMethod:(NSEvent *)theEvent completionHandler:(void(^)(BOOL handled))completionHandler;
-- (BOOL)handleEventByKeyboardLayout:(NSEvent *)theEvent;
-@end
-#endif
-
</del><span class="cx"> using namespace WebKit;
</span><span class="cx"> using namespace WebCore;
</span><span class="cx">
</span><del>-#if !USE(ASYNC_NSTEXTINPUTCLIENT)
-struct WKViewInterpretKeyEventsParameters {
- bool eventInterpretationHadSideEffects;
- bool consumedByIM;
- bool executingSavedKeypressCommands;
- Vector<KeypressCommand>* commands;
-};
-#endif
-
</del><span class="cx"> @interface WKViewData : NSObject {
</span><span class="cx"> @public
</span><span class="cx"> std::unique_ptr<PageClientImpl> _pageClient;
</span><span class="cx"> RefPtr<WebPageProxy> _page;
</span><span class="cx"> std::unique_ptr<WebViewImpl> _impl;
</span><del>-
- // We keep here the event when resending it to
- // the application to distinguish the case of a new event from one
- // that has been already sent to WebCore.
- RetainPtr<NSEvent> _keyDownEventBeingResent;
-#if USE(ASYNC_NSTEXTINPUTCLIENT)
- Vector<KeypressCommand>* _collectedKeypressCommands;
-#else
- WKViewInterpretKeyEventsParameters* _interpretKeyEventsParameters;
-#endif
</del><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> @end
</span><span class="lines">@@ -758,932 +731,116 @@
</span><span class="cx"> return result;
</span><span class="cx"> }
</span><span class="cx">
</span><del>-static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnderline>& result)
-{
- int length = [[string string] length];
-
- int i = 0;
- while (i < length) {
- NSRange range;
- NSDictionary *attrs = [string attributesAtIndex:i longestEffectiveRange:&range inRange:NSMakeRange(i, length - i)];
-
- if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) {
- Color color = Color::black;
- if (NSColor *colorAttr = [attrs objectForKey:NSUnderlineColorAttributeName])
- color = colorFromNSColor([colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]);
- result.append(CompositionUnderline(range.location, NSMaxRange(range), color, [style intValue] > 1));
- }
-
- i = range.location + range.length;
- }
-}
-
-#if USE(ASYNC_NSTEXTINPUTCLIENT)
-
-- (void)_collectKeyboardLayoutCommandsForEvent:(NSEvent *)event to:(Vector<KeypressCommand>&)commands
-{
- if ([event type] != NSKeyDown)
- return;
-
- ASSERT(!_data->_collectedKeypressCommands);
- _data->_collectedKeypressCommands = &commands;
-
- if (NSTextInputContext *context = [self inputContext])
- [context handleEventByKeyboardLayout:event];
- else
- [self interpretKeyEvents:[NSArray arrayWithObject:event]];
-
- _data->_collectedKeypressCommands = nullptr;
-}
-
-- (void)_interpretKeyEvent:(NSEvent *)event completionHandler:(void(^)(BOOL handled, const Vector<KeypressCommand>& commands))completionHandler
-{
- // For regular Web content, input methods run before passing a keydown to DOM, but plug-ins get an opportunity to handle the event first.
- // There is no need to collect commands, as the plug-in cannot execute them.
- if (_data->_impl->pluginComplexTextInputIdentifier()) {
- completionHandler(NO, Vector<KeypressCommand>());
- return;
- }
-
- if (![self inputContext]) {
- Vector<KeypressCommand> commands;
- [self _collectKeyboardLayoutCommandsForEvent:event to:commands];
- completionHandler(NO, commands);
- return;
- }
-
- LOG(TextInput, "-> handleEventByInputMethod:%p %@", event, event);
- [[self inputContext] handleEventByInputMethod:event completionHandler:^(BOOL handled) {
-
- LOG(TextInput, "... handleEventByInputMethod%s handled", handled ? "" : " not");
- if (handled) {
- completionHandler(YES, Vector<KeypressCommand>());
- return;
- }
-
- Vector<KeypressCommand> commands;
- [self _collectKeyboardLayoutCommandsForEvent:event to:commands];
- completionHandler(NO, commands);
- }];
-}
-
</del><span class="cx"> - (void)doCommandBySelector:(SEL)selector
</span><span class="cx"> {
</span><del>- LOG(TextInput, "doCommandBySelector:\"%s\"", sel_getName(selector));
-
- Vector<KeypressCommand>* keypressCommands = _data->_collectedKeypressCommands;
-
- if (keypressCommands) {
- KeypressCommand command(NSStringFromSelector(selector));
- keypressCommands->append(command);
- LOG(TextInput, "...stored");
- _data->_page->registerKeypressCommandName(command.commandName);
- } else {
- // FIXME: Send the command to Editor synchronously and only send it along the
- // responder chain if it's a selector that does not correspond to an editing command.
- [super doCommandBySelector:selector];
- }
</del><ins>+ _data->_impl->doCommandBySelector(selector);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> - (void)insertText:(id)string
</span><span class="cx"> {
</span><del>- // Unlike an NSTextInputClient variant with replacementRange, this NSResponder method is called when there is no input context,
- // so text input processing isn't performed. We are not going to actually insert any text in that case, but saving an insertText
- // command ensures that a keypress event is dispatched as appropriate.
- [self insertText:string replacementRange:NSMakeRange(NSNotFound, 0)];
</del><ins>+ _data->_impl->insertText(string);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> - (void)insertText:(id)string replacementRange:(NSRange)replacementRange
</span><span class="cx"> {
</span><del>- BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
- ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
-
- if (replacementRange.location != NSNotFound)
- LOG(TextInput, "insertText:\"%@\" replacementRange:(%u, %u)", isAttributedString ? [string string] : string, replacementRange.location, replacementRange.length);
- else
- LOG(TextInput, "insertText:\"%@\"", isAttributedString ? [string string] : string);
-
- NSString *text;
- Vector<TextAlternativeWithRange> dictationAlternatives;
-
- bool registerUndoGroup = false;
- if (isAttributedString) {
-#if USE(DICTATION_ALTERNATIVES)
- collectDictationTextAlternatives(string, dictationAlternatives);
-#endif
-#if USE(INSERTION_UNDO_GROUPING)
- registerUndoGroup = shouldRegisterInsertionUndoGroup(string);
-#endif
- // FIXME: We ignore most attributes from the string, so for example inserting from Character Palette loses font and glyph variation data.
- text = [string string];
- } else
- text = string;
-
- // insertText can be called for several reasons:
- // - If it's from normal key event processing (including key bindings), we save the action to perform it later.
- // - If it's from an input method, then we should insert the text now.
- // - If it's sent outside of keyboard event processing (e.g. from Character Viewer, or when confirming an inline input area with a mouse),
- // then we also execute it immediately, as there will be no other chance.
- Vector<KeypressCommand>* keypressCommands = _data->_collectedKeypressCommands;
- if (keypressCommands) {
- ASSERT(replacementRange.location == NSNotFound);
- KeypressCommand command("insertText:", text);
- keypressCommands->append(command);
- LOG(TextInput, "...stored");
- _data->_page->registerKeypressCommandName(command.commandName);
- return;
- }
-
- String eventText = text;
- eventText.replace(NSBackTabCharacter, NSTabCharacter); // same thing is done in KeyEventMac.mm in WebCore
- if (!dictationAlternatives.isEmpty())
- _data->_page->insertDictatedTextAsync(eventText, replacementRange, dictationAlternatives, registerUndoGroup);
- else
- _data->_page->insertTextAsync(eventText, replacementRange, registerUndoGroup);
</del><ins>+ _data->_impl->insertText(string, replacementRange);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-- (void)selectedRangeWithCompletionHandler:(void(^)(NSRange selectedRange))completionHandlerPtr
-{
- RetainPtr<id> completionHandler = adoptNS([completionHandlerPtr copy]);
-
- LOG(TextInput, "selectedRange");
- _data->_page->getSelectedRangeAsync([completionHandler](const EditingRange& editingRangeResult, WebKit::CallbackBase::Error error) {
- void (^completionHandlerBlock)(NSRange) = (void (^)(NSRange))completionHandler.get();
- if (error != WebKit::CallbackBase::Error::None) {
- LOG(TextInput, " ...selectedRange failed.");
- completionHandlerBlock(NSMakeRange(NSNotFound, 0));
- return;
- }
- NSRange result = editingRangeResult;
- if (result.location == NSNotFound)
- LOG(TextInput, " -> selectedRange returned (NSNotFound, %llu)", result.length);
- else
- LOG(TextInput, " -> selectedRange returned (%llu, %llu)", result.location, result.length);
- completionHandlerBlock(result);
- });
-}
-
-- (void)markedRangeWithCompletionHandler:(void(^)(NSRange markedRange))completionHandlerPtr
-{
- RetainPtr<id> completionHandler = adoptNS([completionHandlerPtr copy]);
-
- LOG(TextInput, "markedRange");
- _data->_page->getMarkedRangeAsync([completionHandler](const EditingRange& editingRangeResult, WebKit::CallbackBase::Error error) {
- void (^completionHandlerBlock)(NSRange) = (void (^)(NSRange))completionHandler.get();
- if (error != WebKit::CallbackBase::Error::None) {
- LOG(TextInput, " ...markedRange failed.");
- completionHandlerBlock(NSMakeRange(NSNotFound, 0));
- return;
- }
- NSRange result = editingRangeResult;
- if (result.location == NSNotFound)
- LOG(TextInput, " -> markedRange returned (NSNotFound, %llu)", result.length);
- else
- LOG(TextInput, " -> markedRange returned (%llu, %llu)", result.location, result.length);
- completionHandlerBlock(result);
- });
-}
-
-- (void)hasMarkedTextWithCompletionHandler:(void(^)(BOOL hasMarkedText))completionHandlerPtr
-{
- RetainPtr<id> completionHandler = adoptNS([completionHandlerPtr copy]);
-
- LOG(TextInput, "hasMarkedText");
- _data->_page->getMarkedRangeAsync([completionHandler](const EditingRange& editingRangeResult, WebKit::CallbackBase::Error error) {
- void (^completionHandlerBlock)(BOOL) = (void (^)(BOOL))completionHandler.get();
- if (error != WebKit::CallbackBase::Error::None) {
- LOG(TextInput, " ...hasMarkedText failed.");
- completionHandlerBlock(NO);
- return;
- }
- BOOL hasMarkedText = editingRangeResult.location != notFound;
- LOG(TextInput, " -> hasMarkedText returned %u", hasMarkedText);
- completionHandlerBlock(hasMarkedText);
- });
-}
-
-- (void)attributedSubstringForProposedRange:(NSRange)nsRange completionHandler:(void(^)(NSAttributedString *attrString, NSRange actualRange))completionHandlerPtr
-{
- RetainPtr<id> completionHandler = adoptNS([completionHandlerPtr copy]);
-
- LOG(TextInput, "attributedSubstringFromRange:(%u, %u)", nsRange.location, nsRange.length);
- _data->_page->attributedSubstringForCharacterRangeAsync(nsRange, [completionHandler](const AttributedString& string, const EditingRange& actualRange, WebKit::CallbackBase::Error error) {
- void (^completionHandlerBlock)(NSAttributedString *, NSRange) = (void (^)(NSAttributedString *, NSRange))completionHandler.get();
- if (error != WebKit::CallbackBase::Error::None) {
- LOG(TextInput, " ...attributedSubstringFromRange failed.");
- completionHandlerBlock(0, NSMakeRange(NSNotFound, 0));
- return;
- }
- LOG(TextInput, " -> attributedSubstringFromRange returned %@", [string.string.get() string]);
- completionHandlerBlock([[string.string.get() retain] autorelease], actualRange);
- });
-}
-
-- (void)firstRectForCharacterRange:(NSRange)theRange completionHandler:(void(^)(NSRect firstRect, NSRange actualRange))completionHandlerPtr
-{
- RetainPtr<id> completionHandler = adoptNS([completionHandlerPtr copy]);
-
- LOG(TextInput, "firstRectForCharacterRange:(%u, %u)", theRange.location, theRange.length);
-
- // Just to match NSTextView's behavior. Regression tests cannot detect this;
- // to reproduce, use a test application from http://bugs.webkit.org/show_bug.cgi?id=4682
- // (type something; try ranges (1, -1) and (2, -1).
- if ((theRange.location + theRange.length < theRange.location) && (theRange.location + theRange.length != 0))
- theRange.length = 0;
-
- if (theRange.location == NSNotFound) {
- LOG(TextInput, " -> NSZeroRect");
- completionHandlerPtr(NSZeroRect, theRange);
- return;
- }
-
- _data->_page->firstRectForCharacterRangeAsync(theRange, [self, completionHandler](const IntRect& rect, const EditingRange& actualRange, WebKit::CallbackBase::Error error) {
- void (^completionHandlerBlock)(NSRect, NSRange) = (void (^)(NSRect, NSRange))completionHandler.get();
- if (error != WebKit::CallbackBase::Error::None) {
- LOG(TextInput, " ...firstRectForCharacterRange failed.");
- completionHandlerBlock(NSZeroRect, NSMakeRange(NSNotFound, 0));
- return;
- }
-
- NSRect resultRect = [self convertRect:rect toView:nil];
- resultRect = [self.window convertRectToScreen:resultRect];
-
- LOG(TextInput, " -> firstRectForCharacterRange returned (%f, %f, %f, %f)", resultRect.origin.x, resultRect.origin.y, resultRect.size.width, resultRect.size.height);
- completionHandlerBlock(resultRect, actualRange);
- });
-}
-
-- (void)characterIndexForPoint:(NSPoint)thePoint completionHandler:(void(^)(NSUInteger))completionHandlerPtr
-{
- RetainPtr<id> completionHandler = adoptNS([completionHandlerPtr copy]);
-
- LOG(TextInput, "characterIndexForPoint:(%f, %f)", thePoint.x, thePoint.y);
-
- NSWindow *window = [self window];
-
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- if (window)
- thePoint = [window convertScreenToBase:thePoint];
-#pragma clang diagnostic pop
- thePoint = [self convertPoint:thePoint fromView:nil]; // the point is relative to the main frame
-
- _data->_page->characterIndexForPointAsync(IntPoint(thePoint), [completionHandler](uint64_t result, WebKit::CallbackBase::Error error) {
- void (^completionHandlerBlock)(NSUInteger) = (void (^)(NSUInteger))completionHandler.get();
- if (error != WebKit::CallbackBase::Error::None) {
- LOG(TextInput, " ...characterIndexForPoint failed.");
- completionHandlerBlock(0);
- return;
- }
- if (result == notFound)
- result = NSNotFound;
- LOG(TextInput, " -> characterIndexForPoint returned %lu", result);
- completionHandlerBlock(result);
- });
-}
-
</del><span class="cx"> - (NSTextInputContext *)inputContext
</span><span class="cx"> {
</span><del>- if (_data->_impl->pluginComplexTextInputIdentifier()) {
- ASSERT(!_data->_collectedKeypressCommands); // Should not get here from -_interpretKeyEvent:completionHandler:, we only use WKTextInputWindowController after giving the plug-in a chance to handle keydown natively.
- return [[WKTextInputWindowController sharedTextInputWindowController] inputContext];
- }
-
- // Disable text input machinery when in non-editable content. An invisible inline input area affects performance, and can prevent Expose from working.
- if (!_data->_page->editorState().isContentEditable)
- return nil;
-
- return [super inputContext];
</del><ins>+ return _data->_impl->inputContext();
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-- (void)unmarkText
-{
- LOG(TextInput, "unmarkText");
-
- _data->_page->confirmCompositionAsync();
-}
-
-- (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
-{
- BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
- ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
-
- LOG(TextInput, "setMarkedText:\"%@\" selectedRange:(%u, %u) replacementRange:(%u, %u)", isAttributedString ? [string string] : string, selectedRange.location, selectedRange.length, replacementRange.location, replacementRange.length);
-
- Vector<CompositionUnderline> underlines;
- NSString *text;
-
- if (isAttributedString) {
- // FIXME: We ignore most attributes from the string, so an input method cannot specify e.g. a font or a glyph variation.
- text = [string string];
- extractUnderlines(string, underlines);
- } else
- text = string;
-
- if (_data->_impl->inSecureInputState()) {
- // In password fields, we only allow ASCII dead keys, and don't allow inline input, matching NSSecureTextInputField.
- // Allowing ASCII dead keys is necessary to enable full Roman input when using a Vietnamese keyboard.
- ASSERT(!_data->_page->editorState().hasComposition);
- _data->_impl->notifyInputContextAboutDiscardedComposition();
- // FIXME: We should store the command to handle it after DOM event processing, as it's regular keyboard input now, not a composition.
- if ([text length] == 1 && isASCII([text characterAtIndex:0]))
- _data->_page->insertTextAsync(text, replacementRange);
- else
- NSBeep();
- return;
- }
-
- _data->_page->setCompositionAsync(text, underlines, selectedRange, replacementRange);
-}
-
-// Synchronous NSTextInputClient is still implemented to catch spurious sync calls. Remove when that is no longer needed.
-
-- (NSRange)selectedRange NO_RETURN_DUE_TO_ASSERT
-{
- ASSERT_NOT_REACHED();
- return NSMakeRange(NSNotFound, 0);
-}
-
-- (BOOL)hasMarkedText NO_RETURN_DUE_TO_ASSERT
-{
- ASSERT_NOT_REACHED();
- return NO;
-}
-
-- (NSRange)markedRange NO_RETURN_DUE_TO_ASSERT
-{
- ASSERT_NOT_REACHED();
- return NSMakeRange(NSNotFound, 0);
-}
-
-- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)nsRange actualRange:(NSRangePointer)actualRange NO_RETURN_DUE_TO_ASSERT
-{
- ASSERT_NOT_REACHED();
- return nil;
-}
-
-- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint NO_RETURN_DUE_TO_ASSERT
-{
- ASSERT_NOT_REACHED();
- return 0;
-}
-
-- (NSRect)firstRectForCharacterRange:(NSRange)theRange actualRange:(NSRangePointer)actualRange NO_RETURN_DUE_TO_ASSERT
-{
- ASSERT_NOT_REACHED();
- return NSMakeRect(0, 0, 0, 0);
-}
-
</del><span class="cx"> - (BOOL)performKeyEquivalent:(NSEvent *)event
</span><span class="cx"> {
</span><del>- if (_data->_impl->ignoresNonWheelEvents())
- return NO;
-
- // There's a chance that responding to this event will run a nested event loop, and
- // fetching a new event might release the old one. Retaining and then autoreleasing
- // the current event prevents that from causing a problem inside WebKit or AppKit code.
- [[event retain] autorelease];
-
- // We get Esc key here after processing either Esc or Cmd+period. The former starts as a keyDown, and the latter starts as a key equivalent,
- // but both get transformed to a cancelOperation: command, executing which passes an Esc key event to -performKeyEquivalent:.
- // Don't interpret this event again, avoiding re-entrancy and infinite loops.
- if ([[event charactersIgnoringModifiers] isEqualToString:@"\e"] && !([event modifierFlags] & NSDeviceIndependentModifierFlagsMask))
- return [super performKeyEquivalent:event];
-
- if (_data->_keyDownEventBeingResent) {
- // WebCore has already seen the event, no need for custom processing.
- // Note that we can get multiple events for each event being re-sent. For example, for Cmd+'=' AppKit
- // first performs the original key equivalent, and if that isn't handled, it dispatches a synthetic Cmd+'+'.
- return [super performKeyEquivalent:event];
- }
-
- ASSERT(event == [NSApp currentEvent]);
-
- _data->_impl->disableComplexTextInputIfNecessary();
-
- // Pass key combos through WebCore if there is a key binding available for
- // this event. This lets webpages have a crack at intercepting key-modified keypresses.
- // FIXME: Why is the firstResponder check needed?
- if (self == [[self window] firstResponder]) {
- [self _interpretKeyEvent:event completionHandler:^(BOOL handledByInputMethod, const Vector<KeypressCommand>& commands) {
- _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(event, handledByInputMethod, commands));
- }];
- return YES;
- }
-
- return [super performKeyEquivalent:event];
</del><ins>+ return _data->_impl->performKeyEquivalent(event);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> - (void)keyUp:(NSEvent *)theEvent
</span><span class="cx"> {
</span><del>- if (_data->_impl->ignoresNonWheelEvents())
- return;
-
- LOG(TextInput, "keyUp:%p %@", theEvent, theEvent);
-
- [self _interpretKeyEvent:theEvent completionHandler:^(BOOL handledByInputMethod, const Vector<KeypressCommand>& commands) {
- ASSERT(!handledByInputMethod || commands.isEmpty());
- _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent, handledByInputMethod, commands));
- }];
</del><ins>+ _data->_impl->keyUp(theEvent);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> - (void)keyDown:(NSEvent *)theEvent
</span><span class="cx"> {
</span><del>- if (_data->_impl->ignoresNonWheelEvents())
- return;
-
- LOG(TextInput, "keyDown:%p %@%s", theEvent, theEvent, (theEvent == _data->_keyDownEventBeingResent) ? " (re-sent)" : "");
-
- if (_data->_impl->tryHandlePluginComplexTextInputKeyDown(theEvent)) {
- LOG(TextInput, "...handled by plug-in");
- return;
- }
-
- // We could be receiving a key down from AppKit if we have re-sent an event
- // that maps to an action that is currently unavailable (for example a copy when
- // there is no range selection).
- // If this is the case we should ignore the key down.
- if (_data->_keyDownEventBeingResent == theEvent) {
- [super keyDown:theEvent];
- return;
- }
-
- [self _interpretKeyEvent:theEvent completionHandler:^(BOOL handledByInputMethod, const Vector<KeypressCommand>& commands) {
- ASSERT(!handledByInputMethod || commands.isEmpty());
- _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent, handledByInputMethod, commands));
- }];
</del><ins>+ _data->_impl->keyDown(theEvent);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> - (void)flagsChanged:(NSEvent *)theEvent
</span><span class="cx"> {
</span><del>- if (_data->_impl->ignoresNonWheelEvents())
- return;
-
- LOG(TextInput, "flagsChanged:%p %@", theEvent, theEvent);
-
- unsigned short keyCode = [theEvent keyCode];
-
- // Don't make an event from the num lock and function keys
- if (!keyCode || keyCode == 10 || keyCode == 63)
- return;
-
- [self _interpretKeyEvent:theEvent completionHandler:^(BOOL handledByInputMethod, const Vector<KeypressCommand>& commands) {
- _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent, handledByInputMethod, commands));
- }];
</del><ins>+ _data->_impl->flagsChanged(theEvent);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-#else // USE(ASYNC_NSTEXTINPUTCLIENT)
-
-- (BOOL)_interpretKeyEvent:(NSEvent *)event savingCommandsTo:(Vector<WebCore::KeypressCommand>&)commands
</del><ins>+- (void)setMarkedText:(id)string selectedRange:(NSRange)newSelectedRange replacementRange:(NSRange)replacementRange
</ins><span class="cx"> {
</span><del>- ASSERT(!_data->_interpretKeyEventsParameters);
- ASSERT(commands.isEmpty());
-
- if ([event type] == NSFlagsChanged)
- return NO;
-
- WKViewInterpretKeyEventsParameters parameters;
- parameters.eventInterpretationHadSideEffects = false;
- parameters.executingSavedKeypressCommands = false;
- // We assume that an input method has consumed the event, and only change this assumption if one of the NSTextInput methods is called.
- // We assume the IM will *not* consume hotkey sequences.
- parameters.consumedByIM = !([event modifierFlags] & NSCommandKeyMask);
- parameters.commands = &commands;
- _data->_interpretKeyEventsParameters = &parameters;
-
- LOG(TextInput, "-> interpretKeyEvents:%p %@", event, event);
- [self interpretKeyEvents:[NSArray arrayWithObject:event]];
-
- _data->_interpretKeyEventsParameters = nullptr;
-
- // An input method may consume an event and not tell us (e.g. when displaying a candidate window),
- // in which case we should not bubble the event up the DOM.
- if (parameters.consumedByIM) {
- ASSERT(commands.isEmpty());
- LOG(TextInput, "...event %p was consumed by an input method", event);
- return YES;
- }
-
- LOG(TextInput, "...interpretKeyEvents for event %p done, returns %d", event, parameters.eventInterpretationHadSideEffects);
-
- // If we have already executed all or some of the commands, the event is "handled". Note that there are additional checks on web process side.
- return parameters.eventInterpretationHadSideEffects;
</del><ins>+ _data->_impl->setMarkedText(string, newSelectedRange, replacementRange);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-- (void)_executeSavedKeypressCommands
</del><ins>+- (void)unmarkText
</ins><span class="cx"> {
</span><del>- WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters;
- if (!parameters || parameters->commands->isEmpty())
- return;
-
- // We could be called again if the execution of one command triggers a call to selectedRange.
- // In this case, the state is up to date, and we don't need to execute any more saved commands to return a result.
- if (parameters->executingSavedKeypressCommands)
- return;
-
- LOG(TextInput, "Executing %u saved keypress commands...", parameters->commands->size());
-
- parameters->executingSavedKeypressCommands = true;
- parameters->eventInterpretationHadSideEffects |= _data->_page->executeKeypressCommands(*parameters->commands);
- parameters->commands->clear();
- parameters->executingSavedKeypressCommands = false;
-
- LOG(TextInput, "...done executing saved keypress commands.");
</del><ins>+ _data->_impl->unmarkText();
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-- (void)doCommandBySelector:(SEL)selector
-{
- LOG(TextInput, "doCommandBySelector:\"%s\"", sel_getName(selector));
-
- WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters;
- if (parameters)
- parameters->consumedByIM = false;
-
- // As in insertText:replacementRange:, we assume that the call comes from an input method if there is marked text.
- bool isFromInputMethod = _data->_page->editorState().hasComposition;
-
- if (parameters && !isFromInputMethod) {
- KeypressCommand command(NSStringFromSelector(selector));
- parameters->commands->append(command);
- LOG(TextInput, "...stored");
- _data->_page->registerKeypressCommandName(command.commandName);
- } else {
- // FIXME: Send the command to Editor synchronously and only send it along the
- // responder chain if it's a selector that does not correspond to an editing command.
- [super doCommandBySelector:selector];
- }
-}
-
-- (void)insertText:(id)string
-{
- // Unlike an NSTextInputClient variant with replacementRange, this NSResponder method is called when there is no input context,
- // so text input processing isn't performed. We are not going to actually insert any text in that case, but saving an insertText
- // command ensures that a keypress event is dispatched as appropriate.
- [self insertText:string replacementRange:NSMakeRange(NSNotFound, 0)];
-}
-
-- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
-{
- BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
- ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
-
- if (replacementRange.location != NSNotFound)
- LOG(TextInput, "insertText:\"%@\" replacementRange:(%u, %u)", isAttributedString ? [string string] : string, replacementRange.location, replacementRange.length);
- else
- LOG(TextInput, "insertText:\"%@\"", isAttributedString ? [string string] : string);
- WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters;
- if (parameters)
- parameters->consumedByIM = false;
-
- NSString *text;
- bool isFromInputMethod = _data->_page->editorState().hasComposition;
-
- Vector<TextAlternativeWithRange> dictationAlternatives;
-
- if (isAttributedString) {
-#if USE(DICTATION_ALTERNATIVES)
- collectDictationTextAlternatives(string, dictationAlternatives);
-#endif
- // FIXME: We ignore most attributes from the string, so for example inserting from Character Palette loses font and glyph variation data.
- text = [string string];
- } else
- text = string;
-
- // insertText can be called for several reasons:
- // - If it's from normal key event processing (including key bindings), we may need to save the action to perform it later.
- // - If it's from an input method, then we should insert the text now. We assume it's from the input method if we have marked text.
- // FIXME: In theory, this could be wrong for some input methods, so we should try to find another way to determine if the call is from the input method.
- // - If it's sent outside of keyboard event processing (e.g. from Character Viewer, or when confirming an inline input area with a mouse),
- // then we also execute it immediately, as there will be no other chance.
- if (parameters && !isFromInputMethod) {
- // FIXME: Handle replacementRange in this case, too. It's known to occur in practice when canceling Press and Hold (see <rdar://11940670>).
- ASSERT(replacementRange.location == NSNotFound);
- KeypressCommand command("insertText:", text);
- parameters->commands->append(command);
- _data->_page->registerKeypressCommandName(command.commandName);
- return;
- }
-
- String eventText = text;
- eventText.replace(NSBackTabCharacter, NSTabCharacter); // same thing is done in KeyEventMac.mm in WebCore
- bool eventHandled;
- if (!dictationAlternatives.isEmpty())
- eventHandled = _data->_page->insertDictatedText(eventText, replacementRange, dictationAlternatives);
- else
- eventHandled = _data->_page->insertText(eventText, replacementRange);
-
- if (parameters)
- parameters->eventInterpretationHadSideEffects |= eventHandled;
-}
-
-- (NSTextInputContext *)inputContext
-{
- WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters;
-
- if (_data->_impl->pluginComplexTextInputIdentifier() && !parameters)
- return [[WKTextInputWindowController sharedTextInputWindowController] inputContext];
-
- // Disable text input machinery when in non-editable content. An invisible inline input area affects performance, and can prevent Expose from working.
- if (!_data->_page->editorState().isContentEditable)
- return nil;
-
- return [super inputContext];
-}
-
</del><span class="cx"> - (NSRange)selectedRange
</span><span class="cx"> {
</span><del>- [self _executeSavedKeypressCommands];
-
- EditingRange selectedRange;
- _data->_page->getSelectedRange(selectedRange);
-
- NSRange result = selectedRange;
- if (result.location == NSNotFound)
- LOG(TextInput, "selectedRange -> (NSNotFound, %u)", result.length);
- else
- LOG(TextInput, "selectedRange -> (%u, %u)", result.location, result.length);
-
- return result;
</del><ins>+ return _data->_impl->selectedRange();
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> - (BOOL)hasMarkedText
</span><span class="cx"> {
</span><del>- WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters;
-
- BOOL result;
- if (parameters) {
- result = _data->_page->editorState().hasComposition;
- if (result) {
- // A saved command can confirm a composition, but it cannot start a new one.
- [self _executeSavedKeypressCommands];
- result = _data->_page->editorState().hasComposition;
- }
- } else {
- EditingRange markedRange;
- _data->_page->getMarkedRange(markedRange);
- result = markedRange.location != notFound;
- }
-
- LOG(TextInput, "hasMarkedText -> %u", result);
- return result;
</del><ins>+ return _data->_impl->hasMarkedText();
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-- (void)unmarkText
-{
- [self _executeSavedKeypressCommands];
-
- LOG(TextInput, "unmarkText");
-
- // Use pointer to get parameters passed to us by the caller of interpretKeyEvents.
- WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters;
-
- if (parameters) {
- parameters->eventInterpretationHadSideEffects = true;
- parameters->consumedByIM = false;
- }
-
- _data->_page->confirmComposition();
-}
-
-- (void)setMarkedText:(id)string selectedRange:(NSRange)newSelectedRange replacementRange:(NSRange)replacementRange
-{
- [self _executeSavedKeypressCommands];
-
- BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
- ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
-
- LOG(TextInput, "setMarkedText:\"%@\" selectedRange:(%u, %u)", isAttributedString ? [string string] : string, newSelectedRange.location, newSelectedRange.length);
-
- // Use pointer to get parameters passed to us by the caller of interpretKeyEvents.
- WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters;
-
- if (parameters) {
- parameters->eventInterpretationHadSideEffects = true;
- parameters->consumedByIM = false;
- }
-
- Vector<CompositionUnderline> underlines;
- NSString *text;
-
- if (isAttributedString) {
- // FIXME: We ignore most attributes from the string, so an input method cannot specify e.g. a font or a glyph variation.
- text = [string string];
- extractUnderlines(string, underlines);
- } else
- text = string;
-
- if (_data->_page->editorState().isInPasswordField) {
- // In password fields, we only allow ASCII dead keys, and don't allow inline input, matching NSSecureTextInputField.
- // Allowing ASCII dead keys is necessary to enable full Roman input when using a Vietnamese keyboard.
- ASSERT(!_data->_page->editorState().hasComposition);
- _data->_impl->notifyInputContextAboutDiscardedComposition();
- if ([text length] == 1 && [[text decomposedStringWithCanonicalMapping] characterAtIndex:0] < 0x80) {
- _data->_page->insertText(text, replacementRange);
- } else
- NSBeep();
- return;
- }
-
- _data->_page->setComposition(text, underlines, newSelectedRange, replacementRange);
-}
-
</del><span class="cx"> - (NSRange)markedRange
</span><span class="cx"> {
</span><del>- [self _executeSavedKeypressCommands];
-
- EditingRange markedRange;
- _data->_page->getMarkedRange(markedRange);
-
- NSRange result = markedRange;
- if (result.location == NSNotFound)
- LOG(TextInput, "markedRange -> (NSNotFound, %u)", result.length);
- else
- LOG(TextInput, "markedRange -> (%u, %u)", result.location, result.length);
-
- return result;
</del><ins>+ return _data->_impl->markedRange();
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> - (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)nsRange actualRange:(NSRangePointer)actualRange
</span><span class="cx"> {
</span><del>- [self _executeSavedKeypressCommands];
-
- if (!_data->_page->editorState().isContentEditable) {
- LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> nil", nsRange.location, nsRange.length);
- return nil;
- }
-
- if (_data->_page->editorState().isInPasswordField)
- return nil;
-
- AttributedString result;
- _data->_page->getAttributedSubstringFromRange(nsRange, result);
-
- if (actualRange) {
- *actualRange = nsRange;
- actualRange->length = [result.string length];
- }
-
- LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> \"%@\"", nsRange.location, nsRange.length, [result.string string]);
- return [[result.string retain] autorelease];
</del><ins>+ return _data->_impl->attributedSubstringForProposedRange(nsRange, actualRange);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint
</span><span class="cx"> {
</span><del>- [self _executeSavedKeypressCommands];
-
- NSWindow *window = [self window];
-
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- if (window)
- thePoint = [window convertScreenToBase:thePoint];
-#pragma clang diagnostic pop
- thePoint = [self convertPoint:thePoint fromView:nil]; // the point is relative to the main frame
-
- uint64_t result = _data->_page->characterIndexForPoint(IntPoint(thePoint));
- if (result == notFound)
- result = NSNotFound;
- LOG(TextInput, "characterIndexForPoint:(%f, %f) -> %u", thePoint.x, thePoint.y, result);
- return result;
</del><ins>+ return _data->_impl->characterIndexForPoint(thePoint);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> - (NSRect)firstRectForCharacterRange:(NSRange)theRange actualRange:(NSRangePointer)actualRange
</span><del>-{
- [self _executeSavedKeypressCommands];
</del><ins>+{
+ return _data->_impl->firstRectForCharacterRange(theRange, actualRange);
+}
</ins><span class="cx">
</span><del>- // Just to match NSTextView's behavior. Regression tests cannot detect this;
- // to reproduce, use a test application from http://bugs.webkit.org/show_bug.cgi?id=4682
- // (type something; try ranges (1, -1) and (2, -1).
- if ((theRange.location + theRange.length < theRange.location) && (theRange.location + theRange.length != 0))
- theRange.length = 0;
</del><ins>+#if USE(ASYNC_NSTEXTINPUTCLIENT)
</ins><span class="cx">
</span><del>- if (theRange.location == NSNotFound) {
- if (actualRange)
- *actualRange = theRange;
- LOG(TextInput, "firstRectForCharacterRange:(NSNotFound, %u) -> NSZeroRect", theRange.length);
- return NSZeroRect;
- }
-
- NSRect resultRect = _data->_page->firstRectForCharacterRange(theRange);
- resultRect = [self convertRect:resultRect toView:nil];
- resultRect = [self.window convertRectToScreen:resultRect];
-
- if (actualRange) {
- // FIXME: Update actualRange to match the range of first rect.
- *actualRange = theRange;
- }
-
- LOG(TextInput, "firstRectForCharacterRange:(%u, %u) -> (%f, %f, %f, %f)", theRange.location, theRange.length, resultRect.origin.x, resultRect.origin.y, resultRect.size.width, resultRect.size.height);
- return resultRect;
</del><ins>+- (void)selectedRangeWithCompletionHandler:(void(^)(NSRange selectedRange))completionHandlerPtr
+{
+ _data->_impl->selectedRangeWithCompletionHandler(completionHandlerPtr);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-- (BOOL)performKeyEquivalent:(NSEvent *)event
</del><ins>+- (void)markedRangeWithCompletionHandler:(void(^)(NSRange markedRange))completionHandlerPtr
</ins><span class="cx"> {
</span><del>- if (_data->_impl->ignoresNonWheelEvents())
- return NO;
-
- // There's a chance that responding to this event will run a nested event loop, and
- // fetching a new event might release the old one. Retaining and then autoreleasing
- // the current event prevents that from causing a problem inside WebKit or AppKit code.
- [[event retain] autorelease];
-
- // We get Esc key here after processing either Esc or Cmd+period. The former starts as a keyDown, and the latter starts as a key equivalent,
- // but both get transformed to a cancelOperation: command, executing which passes an Esc key event to -performKeyEquivalent:.
- // Don't interpret this event again, avoiding re-entrancy and infinite loops.
- if ([[event charactersIgnoringModifiers] isEqualToString:@"\e"] && !([event modifierFlags] & NSDeviceIndependentModifierFlagsMask))
- return [super performKeyEquivalent:event];
-
- if (_data->_keyDownEventBeingResent) {
- // WebCore has already seen the event, no need for custom processing.
- // Note that we can get multiple events for each event being re-sent. For example, for Cmd+'=' AppKit
- // first performs the original key equivalent, and if that isn't handled, it dispatches a synthetic Cmd+'+'.
- return [super performKeyEquivalent:event];
- }
-
- ASSERT(event == [NSApp currentEvent]);
-
- _data->_impl->disableComplexTextInputIfNecessary();
-
- // Pass key combos through WebCore if there is a key binding available for
- // this event. This lets webpages have a crack at intercepting key-modified keypresses.
- // FIXME: Why is the firstResponder check needed?
- if (self == [[self window] firstResponder]) {
- Vector<KeypressCommand> commands;
- BOOL handledByInputMethod = [self _interpretKeyEvent:event savingCommandsTo:commands];
- _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(event, handledByInputMethod, commands));
- return YES;
- }
-
- return [super performKeyEquivalent:event];
</del><ins>+ _data->_impl->markedRangeWithCompletionHandler(completionHandlerPtr);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-- (void)keyUp:(NSEvent *)theEvent
</del><ins>+- (void)hasMarkedTextWithCompletionHandler:(void(^)(BOOL hasMarkedText))completionHandlerPtr
</ins><span class="cx"> {
</span><del>- if (_data->_impl->ignoresNonWheelEvents())
- return;
-
- LOG(TextInput, "keyUp:%p %@", theEvent, theEvent);
- // We don't interpret the keyUp event, as this breaks key bindings (see <https://bugs.webkit.org/show_bug.cgi?id=130100>).
- _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent, false, Vector<KeypressCommand>()));
</del><ins>+ _data->_impl->hasMarkedTextWithCompletionHandler(completionHandlerPtr);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-- (void)keyDown:(NSEvent *)theEvent
</del><ins>+- (void)attributedSubstringForProposedRange:(NSRange)nsRange completionHandler:(void(^)(NSAttributedString *attrString, NSRange actualRange))completionHandlerPtr
</ins><span class="cx"> {
</span><del>- if (_data->_impl->ignoresNonWheelEvents())
- return;
-
- LOG(TextInput, "keyDown:%p %@%s", theEvent, theEvent, (theEvent == _data->_keyDownEventBeingResent) ? " (re-sent)" : "");
-
- // There's a chance that responding to this event will run a nested event loop, and
- // fetching a new event might release the old one. Retaining and then autoreleasing
- // the current event prevents that from causing a problem inside WebKit or AppKit code.
- [[theEvent retain] autorelease];
-
- if (_data->_impl->tryHandlePluginComplexTextInputKeyDown(theEvent)) {
- LOG(TextInput, "...handled by plug-in");
- return;
- }
-
- // We could be receiving a key down from AppKit if we have re-sent an event
- // that maps to an action that is currently unavailable (for example a copy when
- // there is no range selection).
- // If this is the case we should ignore the key down.
- if (_data->_keyDownEventBeingResent == theEvent) {
- [super keyDown:theEvent];
- return;
- }
-
- Vector<KeypressCommand> commands;
- BOOL handledByInputMethod = [self _interpretKeyEvent:theEvent savingCommandsTo:commands];
- if (!commands.isEmpty()) {
- // An input method may make several actions per keypress. For example, pressing Return with Korean IM both confirms it and sends a newline.
- // IM-like actions are handled immediately (so the return value from UI process is true), but there are saved commands that
- // should be handled like normal text input after DOM event dispatch.
- handledByInputMethod = NO;
- }
-
- _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent, handledByInputMethod, commands));
</del><ins>+ _data->_impl->attributedSubstringForProposedRange(nsRange, completionHandlerPtr);
</ins><span class="cx"> }
</span><span class="cx">
</span><del>-- (void)flagsChanged:(NSEvent *)theEvent
</del><ins>+- (void)firstRectForCharacterRange:(NSRange)theRange completionHandler:(void(^)(NSRect firstRect, NSRange actualRange))completionHandlerPtr
</ins><span class="cx"> {
</span><del>- if (_data->_impl->ignoresNonWheelEvents())
- return;
</del><ins>+ _data->_impl->firstRectForCharacterRange(theRange, completionHandlerPtr);
+}
</ins><span class="cx">
</span><del>- LOG(TextInput, "flagsChanged:%p %@", theEvent, theEvent);
-
- // There's a chance that responding to this event will run a nested event loop, and
- // fetching a new event might release the old one. Retaining and then autoreleasing
- // the current event prevents that from causing a problem inside WebKit or AppKit code.
- [[theEvent retain] autorelease];
-
- unsigned short keyCode = [theEvent keyCode];
-
- // Don't make an event from the num lock and function keys
- if (!keyCode || keyCode == 10 || keyCode == 63)
- return;
-
- _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent, false, Vector<KeypressCommand>()));
</del><ins>+- (void)characterIndexForPoint:(NSPoint)thePoint completionHandler:(void(^)(NSUInteger))completionHandlerPtr
+{
+ _data->_impl->characterIndexForPoint(thePoint, completionHandlerPtr);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> #endif // USE(ASYNC_NSTEXTINPUTCLIENT)
</span><span class="lines">@@ -1731,6 +888,16 @@
</span><span class="cx"> [super doCommandBySelector:selector];
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+- (BOOL)_superPerformKeyEquivalent:(NSEvent *)event
+{
+ return [super performKeyEquivalent:event];
+}
+
+- (void)_superKeyDown:(NSEvent *)event
+{
+ [super keyDown:event];
+}
+
</ins><span class="cx"> - (NSArray *)validAttributesForMarkedText
</span><span class="cx"> {
</span><span class="cx"> static NSArray *validAttributes;
</span><span class="lines">@@ -1892,30 +1059,6 @@
</span><span class="cx"> _data->_impl->quickLookWithEvent(event);
</span><span class="cx"> }
</span><span class="cx">
</span><del>-- (void)_doneWithKeyEvent:(NSEvent *)event eventWasHandled:(BOOL)eventWasHandled
-{
- if ([event type] != NSKeyDown)
- return;
-
- if (_data->_impl->tryPostProcessPluginComplexTextInputKeyDown(event))
- return;
-
- if (eventWasHandled) {
- [NSCursor setHiddenUntilMouseMoves:YES];
- return;
- }
-
- // resending the event may destroy this WKView
- RetainPtr<WKView> protector(self);
-
- ASSERT(!_data->_keyDownEventBeingResent);
- _data->_keyDownEventBeingResent = event;
- [NSApp _setCurrentEvent:event];
- [NSApp sendEvent:event];
-
- _data->_keyDownEventBeingResent = nullptr;
-}
-
</del><span class="cx"> - (NSTrackingRectTag)addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside
</span><span class="cx"> {
</span><span class="cx"> return _data->_impl->addTrackingRect(NSRectToCGRect(rect), owner, data, assumeInside);
</span></span></pre></div>
<a id="trunkSourceWebKit2UIProcessAPImacWKViewInternalh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebKit2/UIProcess/API/mac/WKViewInternal.h (191790 => 191791)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebKit2/UIProcess/API/mac/WKViewInternal.h        2015-10-30 16:51:19 UTC (rev 191790)
+++ trunk/Source/WebKit2/UIProcess/API/mac/WKViewInternal.h        2015-10-30 17:02:36 UTC (rev 191791)
</span><span class="lines">@@ -50,7 +50,6 @@
</span><span class="cx"> @property (nonatomic, setter=_setThumbnailView:) _WKThumbnailView *_thumbnailView;
</span><span class="cx"> #endif
</span><span class="cx">
</span><del>-- (void)_doneWithKeyEvent:(NSEvent *)event eventWasHandled:(BOOL)eventWasHandled;
</del><span class="cx"> - (void)_addFontPanelObserver;
</span><span class="cx">
</span><span class="cx"> @end
</span></span></pre></div>
<a id="trunkSourceWebKit2UIProcessCocoaWebViewImplh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebKit2/UIProcess/Cocoa/WebViewImpl.h (191790 => 191791)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebKit2/UIProcess/Cocoa/WebViewImpl.h        2015-10-30 16:51:19 UTC (rev 191790)
+++ trunk/Source/WebKit2/UIProcess/Cocoa/WebViewImpl.h        2015-10-30 17:02:36 UTC (rev 191791)
</span><span class="lines">@@ -59,6 +59,8 @@
</span><span class="cx"> - (void)_superSmartMagnifyWithEvent:(NSEvent *)event;
</span><span class="cx"> - (id)_superAccessibilityAttributeValue:(NSString *)attribute;
</span><span class="cx"> - (void)_superDoCommandBySelector:(SEL)selector;
</span><ins>+- (BOOL)_superPerformKeyEquivalent:(NSEvent *)event;
+- (void)_superKeyDown:(NSEvent *)event;
</ins><span class="cx">
</span><span class="cx"> // This is a hack; these things live can live on a category (e.g. WKView (Private)) but WKView itself conforms to this protocol.
</span><span class="cx"> // They're not actually optional.
</span><span class="lines">@@ -74,6 +76,10 @@
</span><span class="cx">
</span><span class="cx"> @end
</span><span class="cx">
</span><ins>+namespace WebCore {
+struct KeyPressCommand;
+}
+
</ins><span class="cx"> namespace WebKit {
</span><span class="cx">
</span><span class="cx"> class DrawingAreaProxy;
</span><span class="lines">@@ -86,6 +92,15 @@
</span><span class="cx"> typedef Vector<RetainPtr<ValidationItem>> ValidationVector;
</span><span class="cx"> typedef HashMap<String, ValidationVector> ValidationMap;
</span><span class="cx">
</span><ins>+#if !USE(ASYNC_NSTEXTINPUTCLIENT)
+struct WKViewInterpretKeyEventsParameters {
+ bool eventInterpretationHadSideEffects;
+ bool consumedByIM;
+ bool executingSavedKeypressCommands;
+ Vector<WebCore::KeypressCommand>* commands;
+};
+#endif
+
</ins><span class="cx"> class WebViewImpl {
</span><span class="cx"> WTF_MAKE_FAST_ALLOCATED;
</span><span class="cx"> WTF_MAKE_NONCOPYABLE(WebViewImpl);
</span><span class="lines">@@ -392,6 +407,33 @@
</span><span class="cx"> void setTotalHeightOfBanners(CGFloat totalHeightOfBanners) { m_totalHeightOfBanners = totalHeightOfBanners; }
</span><span class="cx"> CGFloat totalHeightOfBanners() const { return m_totalHeightOfBanners; }
</span><span class="cx">
</span><ins>+ void doneWithKeyEvent(NSEvent *, bool eventWasHandled);
+ void doCommandBySelector(SEL);
+ void insertText(id string);
+ void insertText(id string, NSRange replacementRange);
+ NSTextInputContext *inputContext();
+ void unmarkText();
+ void setMarkedText(id string, NSRange selectedRange, NSRange replacementRange);
+ NSRange selectedRange();
+ bool hasMarkedText();
+ NSRange markedRange();
+ NSAttributedString *attributedSubstringForProposedRange(NSRange, NSRangePointer actualRange);
+ NSUInteger characterIndexForPoint(NSPoint);
+ NSRect firstRectForCharacterRange(NSRange, NSRangePointer actualRange);
+ bool performKeyEquivalent(NSEvent *);
+ void keyUp(NSEvent *);
+ void keyDown(NSEvent *);
+ void flagsChanged(NSEvent *);
+
+#if USE(ASYNC_NSTEXTINPUTCLIENT)
+ void selectedRangeWithCompletionHandler(void(^)(NSRange));
+ void hasMarkedTextWithCompletionHandler(void(^)(BOOL hasMarkedText));
+ void markedRangeWithCompletionHandler(void(^)(NSRange));
+ void attributedSubstringForProposedRange(NSRange, void(^)(NSAttributedString *attrString, NSRange actualRange));
+ void firstRectForCharacterRange(NSRange, void(^)(NSRect firstRect, NSRange actualRange));
+ void characterIndexForPoint(NSPoint, void(^)(NSUInteger));
+#endif // USE(ASYNC_NSTEXTINPUTCLIENT)
+
</ins><span class="cx"> private:
</span><span class="cx"> WeakPtr<WebViewImpl> createWeakPtr() { return m_weakPtrFactory.createWeakPtr(); }
</span><span class="cx">
</span><span class="lines">@@ -411,6 +453,14 @@
</span><span class="cx">
</span><span class="cx"> void setUserInterfaceItemState(NSString *commandName, bool enabled, int state);
</span><span class="cx">
</span><ins>+#if USE(ASYNC_NSTEXTINPUTCLIENT)
+ void collectKeyboardLayoutCommandsForEvent(NSEvent *, Vector<WebCore::KeypressCommand>&);
+ void interpretKeyEvent(NSEvent *, void(^completionHandler)(BOOL handled, const Vector<WebCore::KeypressCommand>&));
+#else
+ void executeSavedKeypressCommands();
+ bool interpretKeyEvent(NSEvent *, Vector<WebCore::KeypressCommand>&);
+#endif
+
</ins><span class="cx"> NSView <WebViewImplDelegate> *m_view;
</span><span class="cx"> WebPageProxy& m_page;
</span><span class="cx"> PageClient& m_pageClient;
</span><span class="lines">@@ -516,6 +566,16 @@
</span><span class="cx"> CGFloat m_totalHeightOfBanners { 0 };
</span><span class="cx">
</span><span class="cx"> RetainPtr<NSView> m_inspectorAttachmentView;
</span><ins>+
+ // We keep here the event when resending it to
+ // the application to distinguish the case of a new event from one
+ // that has been already sent to WebCore.
+ RetainPtr<NSEvent> m_keyDownEventBeingResent;
+#if USE(ASYNC_NSTEXTINPUTCLIENT)
+ Vector<WebCore::KeypressCommand>* m_collectedKeypressCommands;
+#else
+ WKViewInterpretKeyEventsParameters* m_interpretKeyEventsParameters;
+#endif
</ins><span class="cx"> };
</span><span class="cx">
</span><span class="cx"> } // namespace WebKit
</span></span></pre></div>
<a id="trunkSourceWebKit2UIProcessCocoaWebViewImplmm"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebKit2/UIProcess/Cocoa/WebViewImpl.mm (191790 => 191791)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebKit2/UIProcess/Cocoa/WebViewImpl.mm        2015-10-30 16:51:19 UTC (rev 191790)
+++ trunk/Source/WebKit2/UIProcess/Cocoa/WebViewImpl.mm        2015-10-30 17:02:36 UTC (rev 191791)
</span><span class="lines">@@ -74,6 +74,8 @@
</span><span class="cx"> #import <WebCore/NSWindowSPI.h>
</span><span class="cx"> #import <WebCore/PlatformEventFactoryMac.h>
</span><span class="cx"> #import <WebCore/SoftLinking.h>
</span><ins>+#import <WebCore/TextAlternativeWithRange.h>
+#import <WebCore/TextUndoInsertionMarkupMac.h>
</ins><span class="cx"> #import <WebCore/ViewState.h>
</span><span class="cx"> #import <WebCore/WebActionDisablingCALayerDelegate.h>
</span><span class="cx"> #import <WebCore/WebCoreCALayerExtras.h>
</span><span class="lines">@@ -85,6 +87,15 @@
</span><span class="cx">
</span><span class="cx"> SOFT_LINK_CONSTANT_MAY_FAIL(Lookup, LUNotificationPopoverWillClose, NSString *)
</span><span class="cx">
</span><ins>+// FIXME: Move to an SPI header.
+#if USE(ASYNC_NSTEXTINPUTCLIENT)
+@interface NSTextInputContext (WKNSTextInputContextDetails)
+- (void)handleEvent:(NSEvent *)event completionHandler:(void(^)(BOOL handled))completionHandler;
+- (void)handleEventByInputMethod:(NSEvent *)event completionHandler:(void(^)(BOOL handled))completionHandler;
+- (BOOL)handleEventByKeyboardLayout:(NSEvent *)event;
+@end
+#endif
+
</ins><span class="cx"> @interface WKWindowVisibilityObserver : NSObject {
</span><span class="cx"> NSView *_view;
</span><span class="cx"> WebKit::WebViewImpl *_impl;
</span><span class="lines">@@ -532,7 +543,7 @@
</span><span class="cx"> NSEvent *keyboardEvent = nil;
</span><span class="cx"> if ([event type] == NSKeyDown || [event type] == NSKeyUp)
</span><span class="cx"> keyboardEvent = event;
</span><del>- m_page.setInitialFocus(direction == NSSelectingNext, keyboardEvent != nil, NativeWebKeyboardEvent(keyboardEvent, false, Vector<WebCore::KeypressCommand>()), [](WebKit::CallbackBase::Error) { });
</del><ins>+ m_page.setInitialFocus(direction == NSSelectingNext, keyboardEvent != nil, NativeWebKeyboardEvent(keyboardEvent, false, { }), [](WebKit::CallbackBase::Error) { });
</ins><span class="cx"> }
</span><span class="cx"> return true;
</span><span class="cx"> }
</span><span class="lines">@@ -3068,6 +3079,966 @@
</span><span class="cx"> #endif
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+void WebViewImpl::doneWithKeyEvent(NSEvent *event, bool eventWasHandled)
+{
+ if ([event type] != NSKeyDown)
+ return;
+
+ if (tryPostProcessPluginComplexTextInputKeyDown(event))
+ return;
+
+ if (eventWasHandled) {
+ [NSCursor setHiddenUntilMouseMoves:YES];
+ return;
+ }
+
+ // resending the event may destroy this WKView
+ RetainPtr<NSView> protector(m_view);
+
+ ASSERT(!m_keyDownEventBeingResent);
+ m_keyDownEventBeingResent = event;
+ [NSApp _setCurrentEvent:event];
+ [NSApp sendEvent:event];
+
+ m_keyDownEventBeingResent = nullptr;
+}
+
+static Vector<WebCore::CompositionUnderline> extractUnderlines(NSAttributedString *string)
+{
+ Vector<WebCore::CompositionUnderline> result;
+ int length = string.string.length;
+
+ for (int i = 0; i < length;) {
+ NSRange range;
+ NSDictionary *attrs = [string attributesAtIndex:i longestEffectiveRange:&range inRange:NSMakeRange(i, length - i)];
+
+ if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) {
+ WebCore::Color color = WebCore::Color::black;
+ if (NSColor *colorAttr = [attrs objectForKey:NSUnderlineColorAttributeName])
+ color = WebCore::colorFromNSColor(colorAttr);
+ result.append(WebCore::CompositionUnderline(range.location, NSMaxRange(range), color, style.intValue > 1));
+ }
+
+ i = range.location + range.length;
+ }
+
+ return result;
+}
+
+static bool eventKeyCodeIsZeroOrNumLockOrFn(NSEvent *event)
+{
+ unsigned short keyCode = [event keyCode];
+ return !keyCode || keyCode == 10 || keyCode == 63;
+}
+
+#if USE(ASYNC_NSTEXTINPUTCLIENT)
+
+Vector<WebCore::KeypressCommand> WebViewImpl::collectKeyboardLayoutCommandsForEvent(NSEvent *event)
+{
+ Vector<WebCore::KeypressCommand>& commands;
+
+ if ([event type] != NSKeyDown)
+ return;
+
+ ASSERT(!m_collectedKeypressCommands);
+ m_collectedKeypressCommands = &commands;
+
+ if (NSTextInputContext *context = inputContext())
+ [context handleEventByKeyboardLayout:event];
+ else
+ [m_view interpretKeyEvents:[NSArray arrayWithObject:event]];
+
+ m_collectedKeypressCommands = nullptr;
+
+ return commands;
+}
+
+void WebViewImpl::interpretKeyEvent(NSEvent *event, void(^completionHandler)(BOOL handled, const Vector<WebCore::KeypressCommand>& commands))
+{
+ // For regular Web content, input methods run before passing a keydown to DOM, but plug-ins get an opportunity to handle the event first.
+ // There is no need to collect commands, as the plug-in cannot execute them.
+ if (pluginComplexTextInputIdentifier()) {
+ completionHandler(NO, { });
+ return;
+ }
+
+ if (!inputContext()) {
+ auto commands = collectKeyboardLayoutCommandsForEvent(event);
+ completionHandler(NO, commands);
+ return;
+ }
+
+ LOG(TextInput, "-> handleEventByInputMethod:%p %@", event, event);
+ [inputContext() handleEventByInputMethod:event completionHandler:^(BOOL handled) {
+
+ LOG(TextInput, "... handleEventByInputMethod%s handled", handled ? "" : " not");
+ if (handled) {
+ completionHandler(YES, { });
+ return;
+ }
+
+ auto commands = collectKeyboardLayoutCommandsForEvent(event);
+ completionHandler(NO, commands);
+ }];
+}
+
+void WebViewImpl::doCommandBySelector(SEL selector)
+{
+ LOG(TextInput, "doCommandBySelector:\"%s\"", sel_getName(selector));
+
+ if (auto* keypressCommands = m_collectedKeypressCommands) {
+ WebCore::KeypressCommand command(NSStringFromSelector(selector));
+ keypressCommands->append(command);
+ LOG(TextInput, "...stored");
+ m_page.registerKeypressCommandName(command.commandName);
+ } else {
+ // FIXME: Send the command to Editor synchronously and only send it along the
+ // responder chain if it's a selector that does not correspond to an editing command.
+ [m_view _superDoCommandBySelector:selector];
+ }
+}
+
+void WebViewImpl::insertText(id string)
+{
+ // Unlike an NSTextInputClient variant with replacementRange, this NSResponder method is called when there is no input context,
+ // so text input processing isn't performed. We are not going to actually insert any text in that case, but saving an insertText
+ // command ensures that a keypress event is dispatched as appropriate.
+ insertText(string, NSMakeRange(NSNotFound, 0));
+}
+
+void WebViewImpl::insertText(id string, NSRange replacementRange)
+{
+ BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
+ ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
+
+ if (replacementRange.location != NSNotFound)
+ LOG(TextInput, "insertText:\"%@\" replacementRange:(%u, %u)", isAttributedString ? [string string] : string, replacementRange.location, replacementRange.length);
+ else
+ LOG(TextInput, "insertText:\"%@\"", isAttributedString ? [string string] : string);
+
+ NSString *text;
+ Vector<WebCore::TextAlternativeWithRange> dictationAlternatives;
+
+ bool registerUndoGroup = false;
+ if (isAttributedString) {
+#if USE(DICTATION_ALTERNATIVES)
+ WebCore::collectDictationTextAlternatives(string, dictationAlternatives);
+#endif
+#if USE(INSERTION_UNDO_GROUPING)
+ registerUndoGroup = WebCore::shouldRegisterInsertionUndoGroup(string);
+#endif
+ // FIXME: We ignore most attributes from the string, so for example inserting from Character Palette loses font and glyph variation data.
+ text = [string string];
+ } else
+ text = string;
+
+ // insertText can be called for several reasons:
+ // - If it's from normal key event processing (including key bindings), we save the action to perform it later.
+ // - If it's from an input method, then we should insert the text now.
+ // - If it's sent outside of keyboard event processing (e.g. from Character Viewer, or when confirming an inline input area with a mouse),
+ // then we also execute it immediately, as there will be no other chance.
+ Vector<WebCore::KeypressCommand>* keypressCommands = m_collectedKeypressCommands;
+ if (keypressCommands) {
+ ASSERT(replacementRange.location == NSNotFound);
+ WebCore::KeypressCommand command("insertText:", text);
+ keypressCommands->append(command);
+ LOG(TextInput, "...stored");
+ m_page.registerKeypressCommandName(command.commandName);
+ return;
+ }
+
+ String eventText = text;
+ eventText.replace(NSBackTabCharacter, NSTabCharacter); // same thing is done in KeyEventMac.mm in WebCore
+ if (!dictationAlternatives.isEmpty())
+ m_page.insertDictatedTextAsync(eventText, replacementRange, dictationAlternatives, registerUndoGroup);
+ else
+ m_page.insertTextAsync(eventText, replacementRange, registerUndoGroup);
+}
+
+void WebViewImpl::selectedRangeWithCompletionHandler(void(^completionHandlerPtr)(NSRange selectedRange))
+{
+ auto completionHandler = adoptNS([completionHandlerPtr copy]);
+
+ LOG(TextInput, "selectedRange");
+ m_page.getSelectedRangeAsync([completionHandler](const EditingRange& editingRangeResult, WebKit::CallbackBase::Error error) {
+ void (^completionHandlerBlock)(NSRange) = (void (^)(NSRange))completionHandler.get();
+ if (error != WebKit::CallbackBase::Error::None) {
+ LOG(TextInput, " ...selectedRange failed.");
+ completionHandlerBlock(NSMakeRange(NSNotFound, 0));
+ return;
+ }
+ NSRange result = editingRangeResult;
+ if (result.location == NSNotFound)
+ LOG(TextInput, " -> selectedRange returned (NSNotFound, %llu)", result.length);
+ else
+ LOG(TextInput, " -> selectedRange returned (%llu, %llu)", result.location, result.length);
+ completionHandlerBlock(result);
+ });
+}
+
+void WebViewImpl::markedRangeWithCompletionHandler(void(^completionHandlerPtr)(NSRange markedRange))
+{
+ auto completionHandler = adoptNS([completionHandlerPtr copy]);
+
+ LOG(TextInput, "markedRange");
+ m_page.getMarkedRangeAsync([completionHandler](const EditingRange& editingRangeResult, WebKit::CallbackBase::Error error) {
+ void (^completionHandlerBlock)(NSRange) = (void (^)(NSRange))completionHandler.get();
+ if (error != WebKit::CallbackBase::Error::None) {
+ LOG(TextInput, " ...markedRange failed.");
+ completionHandlerBlock(NSMakeRange(NSNotFound, 0));
+ return;
+ }
+ NSRange result = editingRangeResult;
+ if (result.location == NSNotFound)
+ LOG(TextInput, " -> markedRange returned (NSNotFound, %llu)", result.length);
+ else
+ LOG(TextInput, " -> markedRange returned (%llu, %llu)", result.location, result.length);
+ completionHandlerBlock(result);
+ });
+}
+
+void WebViewImpl::hasMarkedTextWithCompletionHandler(void(^completionHandlerPtr)(BOOL hasMarkedText))
+{
+ auto completionHandler = adoptNS([completionHandlerPtr copy]);
+
+ LOG(TextInput, "hasMarkedText");
+ m_page.getMarkedRangeAsync([completionHandler](const EditingRange& editingRangeResult, WebKit::CallbackBase::Error error) {
+ void (^completionHandlerBlock)(BOOL) = (void (^)(BOOL))completionHandler.get();
+ if (error != WebKit::CallbackBase::Error::None) {
+ LOG(TextInput, " ...hasMarkedText failed.");
+ completionHandlerBlock(NO);
+ return;
+ }
+ BOOL hasMarkedText = editingRangeResult.location != notFound;
+ LOG(TextInput, " -> hasMarkedText returned %u", hasMarkedText);
+ completionHandlerBlock(hasMarkedText);
+ });
+}
+
+void WebViewImpl::attributedSubstringForProposedRange(NSRange proposedRange, void(^completionHandlerPtr)(NSAttributedString *attrString, NSRange actualRange))
+{
+ auto completionHandler = adoptNS([completionHandlerPtr copy]);
+
+ LOG(TextInput, "attributedSubstringFromRange:(%u, %u)", proposedRange.location, proposedRange.length);
+ m_page.attributedSubstringForCharacterRangeAsync(proposedRange, [completionHandler](const AttributedString& string, const EditingRange& actualRange, WebKit::CallbackBase::Error error) {
+ void (^completionHandlerBlock)(NSAttributedString *, NSRange) = (void (^)(NSAttributedString *, NSRange))completionHandler.get();
+ if (error != WebKit::CallbackBase::Error::None) {
+ LOG(TextInput, " ...attributedSubstringFromRange failed.");
+ completionHandlerBlock(0, NSMakeRange(NSNotFound, 0));
+ return;
+ }
+ LOG(TextInput, " -> attributedSubstringFromRange returned %@", [string.string.get() string]);
+ completionHandlerBlock([[string.string.get() retain] autorelease], actualRange);
+ });
+}
+
+void WebViewImpl::firstRectForCharacterRange(NSRange range, void(^completionHandlerPtr)(NSRect firstRect, NSRange actualRange))
+{
+ auto completionHandler = adoptNS([completionHandlerPtr copy]);
+
+ LOG(TextInput, "firstRectForCharacterRange:(%u, %u)", range.location, range.length);
+
+ // Just to match NSTextView's behavior. Regression tests cannot detect this;
+ // to reproduce, use a test application from http://bugs.webkit.org/show_bug.cgi?id=4682
+ // (type something; try ranges (1, -1) and (2, -1).
+ if ((range.location + range.length < range.location) && (range.location + range.length != 0))
+ range.length = 0;
+
+ if (range.location == NSNotFound) {
+ LOG(TextInput, " -> NSZeroRect");
+ completionHandlerPtr(NSZeroRect, range);
+ return;
+ }
+
+ auto weakThis = createWeakPtr();
+ m_page.firstRectForCharacterRangeAsync(range, [weakThis, completionHandler](const WebCore::IntRect& rect, const EditingRange& actualRange, WebKit::CallbackBase::Error error) {
+ if (!weakThis)
+ return;
+
+ void (^completionHandlerBlock)(NSRect, NSRange) = (void (^)(NSRect, NSRange))completionHandler.get();
+ if (error != WebKit::CallbackBase::Error::None) {
+ LOG(TextInput, " ...firstRectForCharacterRange failed.");
+ completionHandlerBlock(NSZeroRect, NSMakeRange(NSNotFound, 0));
+ return;
+ }
+
+ NSRect resultRect = [weakThis->m_view convertRect:rect toView:nil];
+ resultRect = [weakThis->m_view.window convertRectToScreen:resultRect];
+
+ LOG(TextInput, " -> firstRectForCharacterRange returned (%f, %f, %f, %f)", resultRect.origin.x, resultRect.origin.y, resultRect.size.width, resultRect.size.height);
+ completionHandlerBlock(resultRect, actualRange);
+ });
+}
+
+void WebViewImpl::characterIndexForPoint(NSPoint point, void(^completionHandlerPtr)(NSUInteger))
+{
+ auto completionHandler = adoptNS([completionHandlerPtr copy]);
+
+ LOG(TextInput, "characterIndexForPoint:(%f, %f)", point.x, point.y);
+
+ NSWindow *window = m_view.window;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ if (window)
+ point = [window convertScreenToBase:point];
+#pragma clang diagnostic pop
+ point = [m_view convertPoint:point fromView:nil]; // the point is relative to the main frame
+
+ m_page.characterIndexForPointAsync(WebCore::IntPoint(point), [completionHandler](uint64_t result, WebKit::CallbackBase::Error error) {
+ void (^completionHandlerBlock)(NSUInteger) = (void (^)(NSUInteger))completionHandler.get();
+ if (error != WebKit::CallbackBase::Error::None) {
+ LOG(TextInput, " ...characterIndexForPoint failed.");
+ completionHandlerBlock(0);
+ return;
+ }
+ if (result == notFound)
+ result = NSNotFound;
+ LOG(TextInput, " -> characterIndexForPoint returned %lu", result);
+ completionHandlerBlock(result);
+ });
+}
+
+NSTextInputContext *WebViewImpl::inputContext()
+{
+ if (pluginComplexTextInputIdentifier()) {
+ ASSERT(!m_collectedKeypressCommands); // Should not get here from -_interpretKeyEvent:completionHandler:, we only use WKTextInputWindowController after giving the plug-in a chance to handle keydown natively.
+ return [[WKTextInputWindowController sharedTextInputWindowController] inputContext];
+ }
+
+ // Disable text input machinery when in non-editable content. An invisible inline input area affects performance, and can prevent Expose from working.
+ if (!m_page.editorState().isContentEditable)
+ return nil;
+
+ return [m_view _superInputContext];
+}
+
+void WebViewImpl::unmarkText()
+{
+ LOG(TextInput, "unmarkText");
+
+ m_page.confirmCompositionAsync();
+}
+
+void WebViewImpl::setMarkedText(id string, NSRange selectedRange, NSRange replacementRange)
+{
+ BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
+ ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
+
+ LOG(TextInput, "setMarkedText:\"%@\" selectedRange:(%u, %u) replacementRange:(%u, %u)", isAttributedString ? [string string] : string, selectedRange.location, selectedRange.length, replacementRange.location, replacementRange.length);
+
+ Vector<WebCore::CompositionUnderline> underlines;
+ NSString *text;
+
+ if (isAttributedString) {
+ // FIXME: We ignore most attributes from the string, so an input method cannot specify e.g. a font or a glyph variation.
+ text = [string string];
+ underlines = extractUnderlines(string);
+ } else
+ text = string;
+
+ if (inSecureInputState()) {
+ // In password fields, we only allow ASCII dead keys, and don't allow inline input, matching NSSecureTextInputField.
+ // Allowing ASCII dead keys is necessary to enable full Roman input when using a Vietnamese keyboard.
+ ASSERT(!m_page.editorState().hasComposition);
+ notifyInputContextAboutDiscardedComposition();
+ // FIXME: We should store the command to handle it after DOM event processing, as it's regular keyboard input now, not a composition.
+ if ([text length] == 1 && isASCII([text characterAtIndex:0]))
+ m_page.insertTextAsync(text, replacementRange);
+ else
+ NSBeep();
+ return;
+ }
+
+ m_page.setCompositionAsync(text, underlines, selectedRange, replacementRange);
+}
+
+// Synchronous NSTextInputClient is still implemented to catch spurious sync calls. Remove when that is no longer needed.
+
+NSRange WebViewImpl::selectedRange()
+{
+ ASSERT_NOT_REACHED();
+ return NSMakeRange(NSNotFound, 0);
+}
+
+bool WebViewImpl::hasMarkedText()
+{
+ ASSERT_NOT_REACHED();
+ return NO;
+}
+
+NSRange WebViewImpl::markedRange()
+{
+ ASSERT_NOT_REACHED();
+ return NSMakeRange(NSNotFound, 0);
+}
+
+NSAttributedString *WebViewImpl::attributedSubstringForProposedRange(NSRange nsRange, NSRangePointer actualRange)
+{
+ ASSERT_NOT_REACHED();
+ return nil;
+}
+
+NSUInteger WebViewImpl::characterIndexForPoint(NSPoint point)
+{
+ ASSERT_NOT_REACHED();
+ return 0;
+}
+
+NSRect WebViewImpl::firstRectForCharacterRange(NSRange range, NSRangePointer actualRange)
+{
+ ASSERT_NOT_REACHED();
+ return NSZeroRect;
+}
+
+bool WebViewImpl::performKeyEquivalent(NSEvent *event)
+{
+ if (ignoresNonWheelEvents())
+ return NO;
+
+ // There's a chance that responding to this event will run a nested event loop, and
+ // fetching a new event might release the old one. Retaining and then autoreleasing
+ // the current event prevents that from causing a problem inside WebKit or AppKit code.
+ [[event retain] autorelease];
+
+ // We get Esc key here after processing either Esc or Cmd+period. The former starts as a keyDown, and the latter starts as a key equivalent,
+ // but both get transformed to a cancelOperation: command, executing which passes an Esc key event to -performKeyEquivalent:.
+ // Don't interpret this event again, avoiding re-entrancy and infinite loops.
+ if ([[event charactersIgnoringModifiers] isEqualToString:@"\e"] && !([event modifierFlags] & NSDeviceIndependentModifierFlagsMask))
+ return [m_view _superPerformKeyEquivalent:event];
+
+ if (m_keyDownEventBeingResent) {
+ // WebCore has already seen the event, no need for custom processing.
+ // Note that we can get multiple events for each event being re-sent. For example, for Cmd+'=' AppKit
+ // first performs the original key equivalent, and if that isn't handled, it dispatches a synthetic Cmd+'+'.
+ return [m_view _superPerformKeyEquivalent:event];
+ }
+
+ ASSERT(event == [NSApp currentEvent]);
+
+ disableComplexTextInputIfNecessary();
+
+ // Pass key combos through WebCore if there is a key binding available for
+ // this event. This lets webpages have a crack at intercepting key-modified keypresses.
+ // FIXME: Why is the firstResponder check needed?
+ if (m_view == m_view.window.firstResponder) {
+ interpretKeyEvent(event, ^(BOOL handledByInputMethod, const Vector<WebCore::KeypressCommand>& commands) {
+ m_page.handleKeyboardEvent(NativeWebKeyboardEvent(event, handledByInputMethod, commands));
+ });
+ return YES;
+ }
+
+ return [m_view _superPerformKeyEquivalent:event];
+}
+
+void WebViewImpl::keyUp(NSEvent *event)
+{
+ if (ignoresNonWheelEvents())
+ return;
+
+ LOG(TextInput, "keyUp:%p %@", event, event);
+
+ interpretKeyEvent(event, ^(BOOL handledByInputMethod, const Vector<WebCore::KeypressCommand>& commands) {
+ ASSERT(!handledByInputMethod || commands.isEmpty());
+ m_page.handleKeyboardEvent(NativeWebKeyboardEvent(event, handledByInputMethod, commands));
+ });
+}
+
+void WebViewImpl::keyDown(NSEvent *event)
+{
+ if (ignoresNonWheelEvents())
+ return;
+
+ LOG(TextInput, "keyDown:%p %@%s", event, event, (event == m_keyDownEventBeingResent) ? " (re-sent)" : "");
+
+ if (tryHandlePluginComplexTextInputKeyDown(event)) {
+ LOG(TextInput, "...handled by plug-in");
+ return;
+ }
+
+ // We could be receiving a key down from AppKit if we have re-sent an event
+ // that maps to an action that is currently unavailable (for example a copy when
+ // there is no range selection).
+ // If this is the case we should ignore the key down.
+ if (m_keyDownEventBeingResent == event) {
+ [m_view _superKeyDown:event];
+ return;
+ }
+
+ interpretKeyEvent(event, ^(BOOL handledByInputMethod, const Vector<WebCore::KeypressCommand>& commands) {
+ ASSERT(!handledByInputMethod || commands.isEmpty());
+ m_page.handleKeyboardEvent(NativeWebKeyboardEvent(event, handledByInputMethod, commands));
+ });
+}
+
+void WebViewImpl::flagsChanged(NSEvent *event)
+{
+ if (ignoresNonWheelEvents())
+ return;
+
+ LOG(TextInput, "flagsChanged:%p %@", event, event);
+
+ // Don't make an event from the num lock and function keys
+ if (eventKeyCodeIsZeroOrNumLockOrFn(event))
+ return;
+
+ interpretKeyEvent(event, ^(BOOL handledByInputMethod, const Vector<WebCore::KeypressCommand>& commands) {
+ m_page.handleKeyboardEvent(NativeWebKeyboardEvent(event, handledByInputMethod, commands));
+ });
+}
+
+#else // USE(ASYNC_NSTEXTINPUTCLIENT)
+
+bool WebViewImpl::interpretKeyEvent(NSEvent *event, Vector<WebCore::KeypressCommand>& commands)
+{
+ ASSERT(!m_interpretKeyEventsParameters);
+ ASSERT(commands.isEmpty());
+
+ if ([event type] == NSFlagsChanged)
+ return NO;
+
+ WKViewInterpretKeyEventsParameters parameters;
+ parameters.eventInterpretationHadSideEffects = false;
+ parameters.executingSavedKeypressCommands = false;
+ // We assume that an input method has consumed the event, and only change this assumption if one of the NSTextInput methods is called.
+ // We assume the IM will *not* consume hotkey sequences.
+ parameters.consumedByIM = !([event modifierFlags] & NSCommandKeyMask);
+ parameters.commands = &commands;
+ m_interpretKeyEventsParameters = &parameters;
+
+ LOG(TextInput, "-> interpretKeyEvents:%p %@", event, event);
+ [m_view interpretKeyEvents:[NSArray arrayWithObject:event]];
+
+ m_interpretKeyEventsParameters = nullptr;
+
+ // An input method may consume an event and not tell us (e.g. when displaying a candidate window),
+ // in which case we should not bubble the event up the DOM.
+ if (parameters.consumedByIM) {
+ ASSERT(commands.isEmpty());
+ LOG(TextInput, "...event %p was consumed by an input method", event);
+ return YES;
+ }
+
+ LOG(TextInput, "...interpretKeyEvents for event %p done, returns %d", event, parameters.eventInterpretationHadSideEffects);
+
+ // If we have already executed all or some of the commands, the event is "handled". Note that there are additional checks on web process side.
+ return parameters.eventInterpretationHadSideEffects;
+}
+
+void WebViewImpl::executeSavedKeypressCommands()
+{
+ auto* parameters = m_interpretKeyEventsParameters;
+ if (!parameters || parameters->commands->isEmpty())
+ return;
+
+ // We could be called again if the execution of one command triggers a call to selectedRange.
+ // In this case, the state is up to date, and we don't need to execute any more saved commands to return a result.
+ if (parameters->executingSavedKeypressCommands)
+ return;
+
+ LOG(TextInput, "Executing %u saved keypress commands...", parameters->commands->size());
+
+ parameters->executingSavedKeypressCommands = true;
+ parameters->eventInterpretationHadSideEffects |= m_page.executeKeypressCommands(*parameters->commands);
+ parameters->commands->clear();
+ parameters->executingSavedKeypressCommands = false;
+
+ LOG(TextInput, "...done executing saved keypress commands.");
+}
+
+void WebViewImpl::doCommandBySelector(SEL selector)
+{
+ LOG(TextInput, "doCommandBySelector:\"%s\"", sel_getName(selector));
+
+ auto* parameters = m_interpretKeyEventsParameters;
+ if (parameters)
+ parameters->consumedByIM = false;
+
+ // As in insertText:replacementRange:, we assume that the call comes from an input method if there is marked text.
+ bool isFromInputMethod = m_page.editorState().hasComposition;
+
+ if (parameters && !isFromInputMethod) {
+ WebCore::KeypressCommand command(NSStringFromSelector(selector));
+ parameters->commands->append(command);
+ LOG(TextInput, "...stored");
+ m_page.registerKeypressCommandName(command.commandName);
+ } else {
+ // FIXME: Send the command to Editor synchronously and only send it along the
+ // responder chain if it's a selector that does not correspond to an editing command.
+ [m_view _superDoCommandBySelector:selector];
+ }
+}
+
+void WebViewImpl::insertText(id string)
+{
+ // Unlike an NSTextInputClient variant with replacementRange, this NSResponder method is called when there is no input context,
+ // so text input processing isn't performed. We are not going to actually insert any text in that case, but saving an insertText
+ // command ensures that a keypress event is dispatched as appropriate.
+ insertText(string, NSMakeRange(NSNotFound, 0));
+}
+
+void WebViewImpl::insertText(id string, NSRange replacementRange)
+{
+ bool isAttributedString = [string isKindOfClass:[NSAttributedString class]];
+ ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
+
+ if (replacementRange.location != NSNotFound)
+ LOG(TextInput, "insertText:\"%@\" replacementRange:(%u, %u)", isAttributedString ? [string string] : string, replacementRange.location, replacementRange.length);
+ else
+ LOG(TextInput, "insertText:\"%@\"", isAttributedString ? [string string] : string);
+ auto* parameters = m_interpretKeyEventsParameters;
+ if (parameters)
+ parameters->consumedByIM = false;
+
+ NSString *text;
+ bool isFromInputMethod = m_page.editorState().hasComposition;
+
+ Vector<WebCore::TextAlternativeWithRange> dictationAlternatives;
+
+ if (isAttributedString) {
+#if USE(DICTATION_ALTERNATIVES)
+ WebCore::collectDictationTextAlternatives(string, dictationAlternatives);
+#endif
+ // FIXME: We ignore most attributes from the string, so for example inserting from Character Palette loses font and glyph variation data.
+ text = [string string];
+ } else
+ text = string;
+
+ // insertText can be called for several reasons:
+ // - If it's from normal key event processing (including key bindings), we may need to save the action to perform it later.
+ // - If it's from an input method, then we should insert the text now. We assume it's from the input method if we have marked text.
+ // FIXME: In theory, this could be wrong for some input methods, so we should try to find another way to determine if the call is from the input method.
+ // - If it's sent outside of keyboard event processing (e.g. from Character Viewer, or when confirming an inline input area with a mouse),
+ // then we also execute it immediately, as there will be no other chance.
+ if (parameters && !isFromInputMethod) {
+ // FIXME: Handle replacementRange in this case, too. It's known to occur in practice when canceling Press and Hold (see <rdar://11940670>).
+ ASSERT(replacementRange.location == NSNotFound);
+ WebCore::KeypressCommand command("insertText:", text);
+ parameters->commands->append(command);
+ m_page.registerKeypressCommandName(command.commandName);
+ return;
+ }
+
+ String eventText = text;
+ eventText.replace(NSBackTabCharacter, NSTabCharacter); // same thing is done in KeyEventMac.mm in WebCore
+ bool eventHandled;
+ if (!dictationAlternatives.isEmpty())
+ eventHandled = m_page.insertDictatedText(eventText, replacementRange, dictationAlternatives);
+ else
+ eventHandled = m_page.insertText(eventText, replacementRange);
+
+ if (parameters)
+ parameters->eventInterpretationHadSideEffects |= eventHandled;
+}
+
+NSTextInputContext *WebViewImpl::inputContext()
+{
+ auto* parameters = m_interpretKeyEventsParameters;
+
+ if (pluginComplexTextInputIdentifier() && !parameters)
+ return [[WKTextInputWindowController sharedTextInputWindowController] inputContext];
+
+ // Disable text input machinery when in non-editable content. An invisible inline input area affects performance, and can prevent Expose from working.
+ if (!m_page.editorState().isContentEditable)
+ return nil;
+
+ return [m_view _superInputContext];
+}
+
+NSRange WebViewImpl::selectedRange()
+{
+ executeSavedKeypressCommands();
+
+ EditingRange selectedRange;
+ m_page.getSelectedRange(selectedRange);
+
+ NSRange result = selectedRange;
+ if (result.location == NSNotFound)
+ LOG(TextInput, "selectedRange -> (NSNotFound, %u)", result.length);
+ else
+ LOG(TextInput, "selectedRange -> (%u, %u)", result.location, result.length);
+
+ return result;
+}
+
+bool WebViewImpl::hasMarkedText()
+{
+ auto* parameters = m_interpretKeyEventsParameters;
+
+ BOOL result;
+ if (parameters) {
+ result = m_page.editorState().hasComposition;
+ if (result) {
+ // A saved command can confirm a composition, but it cannot start a new one.
+ executeSavedKeypressCommands();
+ result = m_page.editorState().hasComposition;
+ }
+ } else {
+ EditingRange markedRange;
+ m_page.getMarkedRange(markedRange);
+ result = markedRange.location != notFound;
+ }
+
+ LOG(TextInput, "hasMarkedText -> %u", result);
+ return result;
+}
+
+void WebViewImpl::unmarkText()
+{
+ executeSavedKeypressCommands();
+
+ LOG(TextInput, "unmarkText");
+
+ auto* parameters = m_interpretKeyEventsParameters;
+
+ if (parameters) {
+ parameters->eventInterpretationHadSideEffects = true;
+ parameters->consumedByIM = false;
+ }
+
+ m_page.confirmComposition();
+}
+
+void WebViewImpl::setMarkedText(id string, NSRange newSelectedRange, NSRange replacementRange)
+{
+ executeSavedKeypressCommands();
+
+ BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
+ ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
+
+ LOG(TextInput, "setMarkedText:\"%@\" selectedRange:(%u, %u)", isAttributedString ? [string string] : string, newSelectedRange.location, newSelectedRange.length);
+
+ auto* parameters = m_interpretKeyEventsParameters;
+
+ if (parameters) {
+ parameters->eventInterpretationHadSideEffects = true;
+ parameters->consumedByIM = false;
+ }
+
+ Vector<WebCore::CompositionUnderline> underlines;
+ NSString *text;
+
+ if (isAttributedString) {
+ // FIXME: We ignore most attributes from the string, so an input method cannot specify e.g. a font or a glyph variation.
+ text = [string string];
+ underlines = extractUnderlines(string);
+ } else
+ text = string;
+
+ if (m_page.editorState().isInPasswordField) {
+ // In password fields, we only allow ASCII dead keys, and don't allow inline input, matching NSSecureTextInputField.
+ // Allowing ASCII dead keys is necessary to enable full Roman input when using a Vietnamese keyboard.
+ ASSERT(!m_page.editorState().hasComposition);
+ notifyInputContextAboutDiscardedComposition();
+ if ([text length] == 1 && [[text decomposedStringWithCanonicalMapping] characterAtIndex:0] < 0x80) {
+ m_page.insertText(text, replacementRange);
+ } else
+ NSBeep();
+ return;
+ }
+
+ m_page.setComposition(text, underlines, newSelectedRange, replacementRange);
+}
+
+NSRange WebViewImpl::markedRange()
+{
+ executeSavedKeypressCommands();
+
+ EditingRange markedRange;
+ m_page.getMarkedRange(markedRange);
+
+ NSRange result = markedRange;
+ if (result.location == NSNotFound)
+ LOG(TextInput, "markedRange -> (NSNotFound, %u)", result.length);
+ else
+ LOG(TextInput, "markedRange -> (%u, %u)", result.location, result.length);
+
+ return result;
+}
+
+NSAttributedString *WebViewImpl::attributedSubstringForProposedRange(NSRange proposedRange, NSRangePointer actualRange)
+{
+ executeSavedKeypressCommands();
+
+ if (!m_page.editorState().isContentEditable) {
+ LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> nil", proposedRange.location, proposedRange.length);
+ return nil;
+ }
+
+ if (m_page.editorState().isInPasswordField)
+ return nil;
+
+ AttributedString result;
+ m_page.getAttributedSubstringFromRange(proposedRange, result);
+
+ if (actualRange) {
+ *actualRange = proposedRange;
+ actualRange->length = [result.string length];
+ }
+
+ LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> \"%@\"", proposedRange.location, proposedRange.length, [result.string string]);
+ return [[result.string retain] autorelease];
+}
+
+NSUInteger WebViewImpl::characterIndexForPoint(NSPoint point)
+{
+ executeSavedKeypressCommands();
+
+ NSWindow *window = m_view.window;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ if (window)
+ point = [window convertScreenToBase:point];
+#pragma clang diagnostic pop
+ point = [m_view convertPoint:point fromView:nil]; // the point is relative to the main frame
+
+ uint64_t result = m_page.characterIndexForPoint(WebCore::IntPoint(point));
+ if (result == notFound)
+ result = NSNotFound;
+ LOG(TextInput, "characterIndexForPoint:(%f, %f) -> %u", point.x, point.y, result);
+ return result;
+}
+
+NSRect WebViewImpl::firstRectForCharacterRange(NSRange range, NSRangePointer actualRange)
+{
+ executeSavedKeypressCommands();
+
+ // Just to match NSTextView's behavior. Regression tests cannot detect this;
+ // to reproduce, use a test application from http://bugs.webkit.org/show_bug.cgi?id=4682
+ // (type something; try ranges (1, -1) and (2, -1).
+ if ((range.location + range.length < range.location) && (range.location + range.length != 0))
+ range.length = 0;
+
+ if (range.location == NSNotFound) {
+ if (actualRange)
+ *actualRange = range;
+ LOG(TextInput, "firstRectForCharacterRange:(NSNotFound, %u) -> NSZeroRect", range.length);
+ return NSZeroRect;
+ }
+
+ NSRect resultRect = m_page.firstRectForCharacterRange(range);
+ resultRect = [m_view convertRect:resultRect toView:nil];
+ resultRect = [m_view.window convertRectToScreen:resultRect];
+
+ if (actualRange) {
+ // FIXME: Update actualRange to match the range of first rect.
+ *actualRange = range;
+ }
+
+ LOG(TextInput, "firstRectForCharacterRange:(%u, %u) -> (%f, %f, %f, %f)", range.location, range.length, resultRect.origin.x, resultRect.origin.y, resultRect.size.width, resultRect.size.height);
+ return resultRect;
+}
+
+bool WebViewImpl::performKeyEquivalent(NSEvent *event)
+{
+ if (ignoresNonWheelEvents())
+ return false;
+
+ // There's a chance that responding to this event will run a nested event loop, and
+ // fetching a new event might release the old one. Retaining and then autoreleasing
+ // the current event prevents that from causing a problem inside WebKit or AppKit code.
+ [[event retain] autorelease];
+
+ // We get Esc key here after processing either Esc or Cmd+period. The former starts as a keyDown, and the latter starts as a key equivalent,
+ // but both get transformed to a cancelOperation: command, executing which passes an Esc key event to -performKeyEquivalent:.
+ // Don't interpret this event again, avoiding re-entrancy and infinite loops.
+ if ([[event charactersIgnoringModifiers] isEqualToString:@"\e"] && !([event modifierFlags] & NSDeviceIndependentModifierFlagsMask))
+ return [m_view _superPerformKeyEquivalent:event];
+
+ if (m_keyDownEventBeingResent) {
+ // WebCore has already seen the event, no need for custom processing.
+ // Note that we can get multiple events for each event being re-sent. For example, for Cmd+'=' AppKit
+ // first performs the original key equivalent, and if that isn't handled, it dispatches a synthetic Cmd+'+'.
+ return [m_view _superPerformKeyEquivalent:event];
+ }
+
+ ASSERT(event == [NSApp currentEvent]);
+
+ disableComplexTextInputIfNecessary();
+
+ // Pass key combos through WebCore if there is a key binding available for
+ // this event. This lets webpages have a crack at intercepting key-modified keypresses.
+ // FIXME: Why is the firstResponder check needed?
+ if (m_view == m_view.window.firstResponder) {
+ Vector<WebCore::KeypressCommand> commands;
+ bool handledByInputMethod = interpretKeyEvent(event, commands);
+ m_page.handleKeyboardEvent(NativeWebKeyboardEvent(event, handledByInputMethod, commands));
+ return true;
+ }
+
+ return [m_view _superPerformKeyEquivalent:event];
+}
+
+void WebViewImpl::keyUp(NSEvent *event)
+{
+ if (ignoresNonWheelEvents())
+ return;
+
+ LOG(TextInput, "keyUp:%p %@", event, event);
+ // We don't interpret the keyUp event, as this breaks key bindings (see <https://bugs.webkit.org/show_bug.cgi?id=130100>).
+ m_page.handleKeyboardEvent(NativeWebKeyboardEvent(event, false, { }));
+}
+
+void WebViewImpl::keyDown(NSEvent *event)
+{
+ if (ignoresNonWheelEvents())
+ return;
+
+ LOG(TextInput, "keyDown:%p %@%s", event, event, (event == m_keyDownEventBeingResent) ? " (re-sent)" : "");
+
+ // There's a chance that responding to this event will run a nested event loop, and
+ // fetching a new event might release the old one. Retaining and then autoreleasing
+ // the current event prevents that from causing a problem inside WebKit or AppKit code.
+ [[event retain] autorelease];
+
+ if (tryHandlePluginComplexTextInputKeyDown(event)) {
+ LOG(TextInput, "...handled by plug-in");
+ return;
+ }
+
+ // We could be receiving a key down from AppKit if we have re-sent an event
+ // that maps to an action that is currently unavailable (for example a copy when
+ // there is no range selection).
+ // If this is the case we should ignore the key down.
+ if (m_keyDownEventBeingResent == event) {
+ [m_view _superKeyDown:event];
+ return;
+ }
+
+ Vector<WebCore::KeypressCommand> commands;
+ bool handledByInputMethod = interpretKeyEvent(event, commands);
+ if (!commands.isEmpty()) {
+ // An input method may make several actions per keypress. For example, pressing Return with Korean IM both confirms it and sends a newline.
+ // IM-like actions are handled immediately (so the return value from UI process is true), but there are saved commands that
+ // should be handled like normal text input after DOM event dispatch.
+ handledByInputMethod = false;
+ }
+
+ m_page.handleKeyboardEvent(NativeWebKeyboardEvent(event, handledByInputMethod, commands));
+}
+
+void WebViewImpl::flagsChanged(NSEvent *event)
+{
+ if (ignoresNonWheelEvents())
+ return;
+
+ LOG(TextInput, "flagsChanged:%p %@", event, 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];
+
+ // Don't make an event from the num lock and function keys
+ if (eventKeyCodeIsZeroOrNumLockOrFn(event))
+ return;
+
+ m_page.handleKeyboardEvent(NativeWebKeyboardEvent(event, false, { }));
+}
+
+#endif // USE(ASYNC_NSTEXTINPUTCLIENT)
+
</ins><span class="cx"> } // namespace WebKit
</span><span class="cx">
</span><span class="cx"> #endif // PLATFORM(MAC)
</span></span></pre></div>
<a id="trunkSourceWebKit2UIProcessmacPageClientImplmm"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebKit2/UIProcess/mac/PageClientImpl.mm (191790 => 191791)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebKit2/UIProcess/mac/PageClientImpl.mm        2015-10-30 16:51:19 UTC (rev 191790)
+++ trunk/Source/WebKit2/UIProcess/mac/PageClientImpl.mm        2015-10-30 17:02:36 UTC (rev 191791)
</span><span class="lines">@@ -435,7 +435,7 @@
</span><span class="cx">
</span><span class="cx"> void PageClientImpl::doneWithKeyEvent(const NativeWebKeyboardEvent& event, bool eventWasHandled)
</span><span class="cx"> {
</span><del>- [m_wkView _doneWithKeyEvent:event.nativeEvent() eventWasHandled:eventWasHandled];
</del><ins>+ m_impl->doneWithKeyEvent(event.nativeEvent(), eventWasHandled);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> RefPtr<WebPopupMenuProxy> PageClientImpl::createPopupMenuProxy(WebPageProxy& page)
</span></span></pre>
</div>
</div>
</body>
</html>