4eb55df46f97bdd35b42e3fe2dfc0f98eff273e0
[WebKit-https.git] / Tools / DumpRenderTree / mac / TextInputControllerMac.m
1 /*
2  * Copyright (C) 2005, 2007, 2016 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer. 
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution. 
13  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #import "config.h"
30 #import "TextInputController.h"
31
32 #if PLATFORM(MAC)
33
34 #import "DumpRenderTreeMac.h"
35 #import <AppKit/NSInputManager.h>
36 #import <AppKit/NSTextAlternatives.h>
37
38 #define SUPPORT_INSERTION_UNDO_GROUPING
39 #if __has_include(<AppKit/NSTextInputContext_Private.h>)
40 #import <AppKit/NSTextInputContext_Private.h>
41 #else
42 NSString *NSTextInsertionUndoableAttributeName;
43 #endif
44
45 #import <WebKit/WebDocument.h>
46 #import <WebKit/WebFrame.h>
47 #import <WebKit/WebFramePrivate.h>
48 #import <WebKit/WebFrameView.h>
49 #import <WebKit/WebHTMLViewPrivate.h>
50 #import <WebKit/WebScriptObject.h>
51 #import <WebKit/WebTypesInternal.h>
52 #import <WebKit/WebView.h>
53 #import <wtf/mac/AppKitCompatibilityDeclarations.h>
54
55 @interface TextInputController (DumpRenderTreeInputMethodHandler)
56 - (BOOL)interpretKeyEvents:(NSArray *)eventArray withSender:(WebHTMLView *)sender;
57 @end
58
59 @interface WebHTMLView (DumpRenderTreeInputMethodHandler)
60 - (void)interpretKeyEvents:(NSArray *)eventArray;
61 @end
62
63 @interface WebHTMLView (WebKitSecretsTextInputControllerIsAwareOf)
64 - (WebFrame *)_frame;
65 - (NSAttributedString *)_attributedStringFromDOMRange:(DOMRange *)range;
66 @end
67
68 @implementation WebHTMLView (DumpRenderTreeInputMethodHandler)
69 - (void)interpretKeyEvents:(NSArray *)eventArray
70 {
71     WebScriptObject *obj = [[self _frame] windowObject];
72     TextInputController *tic = [obj valueForKey:@"textInputController"];
73     if (![tic interpretKeyEvents:eventArray withSender:self])
74         [super interpretKeyEvents:eventArray];
75 }
76 @end
77
78 @interface WebNSRange : NSObject {
79 @private
80     NSRange _range;
81 }
82 - (id)initWithNSRange:(NSRange)range;
83 - (unsigned)location;
84 - (unsigned)length;
85 @end
86
87 @implementation WebNSRange
88
89 - (id)initWithNSRange:(NSRange)range
90 {
91     self = [super init];
92     if (!self)
93         return self;
94
95     _range = range;
96     return self;
97 }
98
99 - (unsigned)location
100 {
101     return _range.location;
102 }
103
104 - (unsigned)length
105 {
106     return _range.length;
107 }
108
109 + (BOOL)isSelectorExcludedFromWebScript:(SEL)selector
110 {
111     return !(selector == @selector(location) || selector == @selector(length));
112 }
113
114 @end
115
116 @implementation NSMutableAttributedString (TextInputController)
117
118 + (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
119 {
120     if (aSelector == @selector(string)
121         || aSelector == @selector(getLength)
122         || aSelector == @selector(ranges)
123         || aSelector == @selector(attributeNamesAtIndex:)
124         || aSelector == @selector(valueOfAttribute:atIndex:)
125         || aSelector == @selector(addAttribute:value:)
126         || aSelector == @selector(addAttribute:value:from:length:)
127         || aSelector == @selector(addColorAttribute:red:green:blue:alpha:)
128         || aSelector == @selector(addColorAttribute:red:green:blue:alpha:from:length:)
129         || aSelector == @selector(addFontAttribute:fontName:size:)
130         || aSelector == @selector(addFontAttribute:fontName:size:from:length:))
131         return NO;
132     return YES;
133 }
134
135 + (NSString *)webScriptNameForSelector:(SEL)aSelector
136 {
137     if (aSelector == @selector(getLength))
138         return @"length";
139     if (aSelector == @selector(ranges))
140         return @"ranges";
141     if (aSelector == @selector(attributeNamesAtIndex:))
142         return @"getAttributeNamesAtIndex";
143     if (aSelector == @selector(valueOfAttribute:atIndex:))
144         return @"getAttributeValueAtIndex";
145     if (aSelector == @selector(addAttribute:value:))
146         return @"addAttribute";
147     if (aSelector == @selector(addAttribute:value:from:length:))
148         return @"addAttributeForRange";
149     if (aSelector == @selector(addColorAttribute:red:green:blue:alpha:))
150         return @"addColorAttribute";
151     if (aSelector == @selector(addColorAttribute:red:green:blue:alpha:from:length:))
152         return @"addColorAttributeForRange";
153     if (aSelector == @selector(addFontAttribute:fontName:size:))
154         return @"addFontAttribute";
155     if (aSelector == @selector(addFontAttribute:fontName:size:from:length:))
156         return @"addFontAttributeForRange";
157
158     return nil;
159 }
160
161 - (int)getLength
162 {
163     return (int)[self length];
164 }
165
166 - (NSArray *)ranges
167 {
168     NSMutableArray *array = [NSMutableArray array];
169     [self enumerateAttributesInRange:NSMakeRange(0, [self length]) options:0 usingBlock:^(NSDictionary *attributes, NSRange range, BOOL *stop) {
170         WebNSRange *webRange = [[WebNSRange alloc] initWithNSRange:range];
171         [array addObject:webRange];
172         [webRange release];
173     }];
174     return array;
175 }
176
177 - (NSArray *)attributeNamesAtIndex:(int)index
178 {
179     NSDictionary *attributes = [self attributesAtIndex:(unsigned)index effectiveRange:nil];
180     return [attributes allKeys];
181 }
182
183 - (id)valueOfAttribute:(NSString *)attrName atIndex:(int)index
184 {
185     return [self attribute:attrName atIndex:(unsigned)index effectiveRange:nil];
186 }
187
188 - (void)addAttribute:(NSString *)attrName value:(id)value
189 {
190     [self addAttribute:attrName value:value range:NSMakeRange(0, [self length])];
191 }
192
193 - (void)addAttribute:(NSString *)attrName value:(id)value from:(int)from length:(int)length
194 {
195     [self addAttribute:attrName value:value range:NSMakeRange((unsigned)from, (unsigned)length)];
196 }
197
198 - (void)addColorAttribute:(NSString *)attrName red:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
199 {
200     [self addAttribute:attrName value:[NSColor colorWithDeviceRed:red green:green blue:blue alpha:alpha] range:NSMakeRange(0, [self length])];
201 }
202
203 - (void)addColorAttribute:(NSString *)attrName red:(float)red green:(float)green blue:(float)blue alpha:(float)alpha from:(int)from length:(int)length
204 {
205     [self addAttribute:attrName value:[NSColor colorWithDeviceRed:red green:green blue:blue alpha:alpha] range:NSMakeRange((unsigned)from, (unsigned)length)];
206 }
207
208 - (void)addFontAttribute:(NSString *)attrName fontName:(NSString *)fontName size:(float)fontSize
209 {
210     [self addAttribute:attrName value:[NSFont fontWithName:fontName size:fontSize] range:NSMakeRange(0, [self length])];
211 }
212
213 - (void)addFontAttribute:(NSString *)attrName fontName:(NSString *)fontName size:(float)fontSize from:(int)from length:(int)length
214 {
215     [self addAttribute:attrName value:[NSFont fontWithName:fontName size:fontSize] range:NSMakeRange((unsigned)from, (unsigned)length)];
216 }
217
218 @end
219
220 @implementation TextInputController
221
222 + (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
223 {
224     if (aSelector == @selector(insertText:)
225         || aSelector == @selector(doCommand:)
226         || aSelector == @selector(setMarkedText:selectedFrom:length:)
227         || aSelector == @selector(unmarkText)
228         || aSelector == @selector(hasMarkedText)
229         || aSelector == @selector(conversationIdentifier)
230         || aSelector == @selector(substringFrom:length:)
231         || aSelector == @selector(attributedSubstringFrom:length:)
232         || aSelector == @selector(legacyAttributedString:)
233         || aSelector == @selector(markedRange)
234         || aSelector == @selector(selectedRange)
235         || aSelector == @selector(firstRectForCharactersFrom:length:)
236         || aSelector == @selector(characterIndexForPointX:Y:)
237         || aSelector == @selector(validAttributesForMarkedText)
238         || aSelector == @selector(attributedStringWithString:)
239         || aSelector == @selector(setInputMethodHandler:)
240         || aSelector == @selector(dictatedStringWithPrimaryString:alternative:alternativeOffset:alternativeLength:)
241         || aSelector == @selector(stringWithUndoGroupingInsertion:))
242         return NO;
243     return YES;
244 }
245
246 + (NSString *)webScriptNameForSelector:(SEL)aSelector
247 {
248     if (aSelector == @selector(insertText:))
249         return @"insertText";
250     if (aSelector == @selector(doCommand:))
251         return @"doCommand";
252     if (aSelector == @selector(setMarkedText:selectedFrom:length:))
253         return @"setMarkedText";
254     if (aSelector == @selector(substringFrom:length:))
255         return @"substringFromRange";
256     if (aSelector == @selector(attributedSubstringFrom:length:))
257         return @"attributedSubstringFromRange";
258     if (aSelector == @selector(legacyAttributedString:))
259         return @"legacyAttributedString";
260     if (aSelector == @selector(firstRectForCharactersFrom:length:))
261         return @"firstRectForCharacterRange";
262     if (aSelector == @selector(characterIndexForPointX:Y:))
263         return @"characterIndexForPoint";
264     if (aSelector == @selector(attributedStringWithString:))
265         return @"makeAttributedString"; // just a factory method, doesn't call into NSTextInput
266     if (aSelector == @selector(setInputMethodHandler:))
267         return @"setInputMethodHandler";
268     if (aSelector == @selector(dictatedStringWithPrimaryString:alternative:alternativeOffset:alternativeLength:))
269         return @"makeDictatedString";
270     if (aSelector == @selector(stringWithUndoGroupingInsertion:))
271         return @"makeUndoGroupingInsertionString";
272
273     return nil;
274 }
275
276 - (id)initWithWebView:(WebView *)wv
277 {
278     self = [super init];
279     webView = wv;
280     inputMethodView = nil;
281     inputMethodHandler = nil;
282     return self;
283 }
284
285 - (void)dealloc
286 {
287     [inputMethodHandler release];
288     inputMethodHandler = nil;
289     
290     [super dealloc];
291 }
292
293 - (NSObject <NSTextInput> *)textInput
294 {
295     NSView <NSTextInput> *view = inputMethodView ? inputMethodView : (id)[[[webView mainFrame] frameView] documentView];
296     return [view conformsToProtocol:@protocol(NSTextInput)] ? view : nil;
297 }
298
299 - (void)insertText:(id)aString
300 {
301     NSObject <NSTextInput> *textInput = [self textInput];
302
303     if (textInput)
304         [textInput insertText:aString];
305 }
306
307 - (void)doCommand:(NSString *)aCommand
308 {
309     NSObject <NSTextInput> *textInput = [self textInput];
310
311     if (textInput)
312         [textInput doCommandBySelector:NSSelectorFromString(aCommand)];
313 }
314
315 - (void)setMarkedText:(NSString *)aString selectedFrom:(int)from length:(int)length
316 {
317     NSObject <NSTextInput> *textInput = [self textInput];
318
319     if (textInput)
320         [textInput setMarkedText:aString selectedRange:NSMakeRange(from, length)];
321 }
322
323 - (void)unmarkText
324 {
325     NSObject <NSTextInput> *textInput = [self textInput];
326
327     if (textInput)
328         [textInput unmarkText];
329 }
330
331 - (BOOL)hasMarkedText
332 {
333     NSObject <NSTextInput> *textInput = [self textInput];
334
335     if (textInput)
336         return [textInput hasMarkedText];
337
338     return FALSE;
339 }
340
341 - (long)conversationIdentifier
342 {
343     NSObject <NSTextInput> *textInput = [self textInput];
344
345     if (textInput)
346         return [textInput conversationIdentifier];
347
348     return 0;
349 }
350
351 - (NSString *)substringFrom:(int)from length:(int)length
352 {
353     NSObject <NSTextInput> *textInput = [self textInput];
354
355     if (textInput)
356         return [[textInput attributedSubstringFromRange:NSMakeRange(from, length)] string];
357     
358     return @"";
359 }
360
361 - (NSMutableAttributedString *)attributedSubstringFrom:(int)from length:(int)length
362 {
363     NSObject <NSTextInput> *textInput = [self textInput];
364
365     NSMutableAttributedString *ret = [[[NSMutableAttributedString alloc] init] autorelease];
366
367     if (textInput)
368         [ret setAttributedString:[textInput attributedSubstringFromRange:NSMakeRange(from, length)]];
369     
370     return ret;
371 }
372
373 - (NSMutableAttributedString *)legacyAttributedString:(DOMRange*)range
374 {
375     NSMutableAttributedString *string = [[[NSMutableAttributedString alloc] init] autorelease];
376     id documentView = [[[webView mainFrame] frameView] documentView];
377     if (![documentView isKindOfClass:[WebHTMLView class]])
378         return string;
379
380     [string setAttributedString:[(WebHTMLView *)documentView _attributedStringFromDOMRange:range]];
381     return string;
382 }
383
384 - (NSArray *)markedRange
385 {
386     NSObject <NSTextInput> *textInput = [self textInput];
387
388     if (textInput) {
389         NSRange range = [textInput markedRange];
390         return [NSArray arrayWithObjects:[NSNumber numberWithUnsignedInt:range.location], [NSNumber numberWithUnsignedInt:range.length], nil];
391     }
392
393     return nil;
394 }
395
396 - (NSArray *)selectedRange
397 {
398     NSObject <NSTextInput> *textInput = [self textInput];
399
400     if (textInput) {
401         NSRange range = [textInput selectedRange];
402         return [NSArray arrayWithObjects:[NSNumber numberWithUnsignedInt:range.location], [NSNumber numberWithUnsignedInt:range.length], nil];
403     }
404
405     return nil;
406 }
407
408 - (NSArray *)firstRectForCharactersFrom:(int)from length:(int)length
409 {
410     NSObject <NSTextInput> *textInput = [self textInput];
411
412     if (textInput) {
413         NSRect rect = [textInput firstRectForCharacterRange:NSMakeRange(from, length)];
414         if (rect.origin.x || rect.origin.y || rect.size.width || rect.size.height) {
415             rect.origin = [[webView window] convertScreenToBase:rect.origin];
416             rect = [webView convertRect:rect fromView:nil];
417         }
418         return [NSArray arrayWithObjects:
419             [NSNumber numberWithFloat:rect.origin.x],
420             [NSNumber numberWithFloat:rect.origin.y],
421             [NSNumber numberWithFloat:rect.size.width],
422             [NSNumber numberWithFloat:rect.size.height],
423             nil];
424     }
425
426     return nil;
427 }
428
429 - (NSInteger)characterIndexForPointX:(float)x Y:(float)y
430 {
431     NSObject <NSTextInput> *textInput = [self textInput];
432
433     if (textInput) {
434         NSPoint point = NSMakePoint(x, y);
435         point = [webView convertPoint:point toView:nil];
436         point = [[webView window] convertBaseToScreen:point];
437         NSInteger index = [textInput characterIndexForPoint:point];
438         if (index == NSNotFound)
439             return -1;
440
441         return index;
442     }
443
444     return 0;
445 }
446
447 - (NSArray *)validAttributesForMarkedText
448 {
449     NSObject <NSTextInput> *textInput = [self textInput];
450
451     if (textInput)
452         return [textInput validAttributesForMarkedText];
453
454     return nil;
455 }
456
457 - (NSMutableAttributedString *)attributedStringWithString:(NSString *)aString
458 {
459     return [[[NSMutableAttributedString alloc] initWithString:aString] autorelease];
460 }
461
462 - (NSMutableAttributedString*)stringWithUndoGroupingInsertion:(NSString*)aString
463 {
464 #if defined(SUPPORT_INSERTION_UNDO_GROUPING)
465     NSMutableAttributedString* attributedString = [self dictatedStringWithPrimaryString:aString alternative:@"test" alternativeOffset:0 alternativeLength:1];
466     [attributedString addAttribute:NSTextInsertionUndoableAttributeName value:@YES range:NSMakeRange(0, [attributedString length])];
467     return attributedString;
468 #else
469     return nil;
470 #endif
471 }
472
473 - (NSMutableAttributedString*)dictatedStringWithPrimaryString:(NSString*)aString alternative:(NSString*)alternative alternativeOffset:(int)offset alternativeLength:(int)length
474 {
475     NSMutableAttributedString* dictatedString = [self attributedStringWithString:aString];
476     NSRange rangeWithAlternative = NSMakeRange((NSUInteger)offset, (NSUInteger)length);
477     NSString* subStringWithAlternative = [aString substringWithRange:rangeWithAlternative];
478     if (!subStringWithAlternative)
479         return nil;
480
481     NSTextAlternatives* alternativeObject = [[[NSTextAlternatives alloc] initWithPrimaryString:subStringWithAlternative alternativeStrings:[NSArray arrayWithObject:alternative]] autorelease];
482     if (!alternativeObject)
483         return nil;
484
485     [dictatedString addAttribute:NSTextAlternativesAttributeName value:alternativeObject range:rangeWithAlternative];
486
487     return dictatedString;
488 }
489
490 - (void)setInputMethodHandler:(WebScriptObject *)handler
491 {
492     if (inputMethodHandler == handler)
493         return;
494     [handler retain];
495     [inputMethodHandler release];
496     inputMethodHandler = handler;
497 }
498
499 - (BOOL)interpretKeyEvents:(NSArray *)eventArray withSender:(WebHTMLView *)sender
500 {
501     if (!inputMethodHandler)
502         return NO;
503     
504     inputMethodView = sender;
505     
506     NSEvent *event = [eventArray objectAtIndex:0];
507     unsigned modifierFlags = [event modifierFlags]; 
508     NSMutableArray *modifiers = [[NSMutableArray alloc] init];
509     if (modifierFlags & NSEventModifierFlagCapsLock)
510         [modifiers addObject:@"NSAlphaShiftKeyMask"];
511     if (modifierFlags & NSEventModifierFlagShift)
512         [modifiers addObject:@"NSShiftKeyMask"];
513     if (modifierFlags & NSEventModifierFlagControl)
514         [modifiers addObject:@"NSControlKeyMask"];
515     if (modifierFlags & NSEventModifierFlagOption)
516         [modifiers addObject:@"NSAlternateKeyMask"];
517     if (modifierFlags & NSEventModifierFlagCommand)
518         [modifiers addObject:@"NSCommandKeyMask"];
519     if (modifierFlags & NSEventModifierFlagNumericPad)
520         [modifiers addObject:@"NSNumericPadKeyMask"];
521     if (modifierFlags & NSEventModifierFlagHelp)
522         [modifiers addObject:@"NSHelpKeyMask"];
523     if (modifierFlags & NSEventModifierFlagFunction)
524         [modifiers addObject:@"NSFunctionKeyMask"];
525     
526     WebScriptObject* eventParam = [inputMethodHandler evaluateWebScript:@"new Object();"];
527     [eventParam setValue:[event characters] forKey:@"characters"];
528     [eventParam setValue:[event charactersIgnoringModifiers] forKey:@"charactersIgnoringModifiers"];
529     [eventParam setValue:[NSNumber numberWithBool:[event isARepeat]] forKey:@"isARepeat"];
530     [eventParam setValue:[NSNumber numberWithUnsignedShort:[event keyCode]] forKey:@"keyCode"];
531     [eventParam setValue:modifiers forKey:@"modifierFlags"];
532
533     [modifiers release];
534     
535     id result = [inputMethodHandler callWebScriptMethod:@"call" withArguments:[NSArray arrayWithObjects:inputMethodHandler, eventParam, nil]];
536     if (![result respondsToSelector:@selector(boolValue)] || ![result boolValue]) {
537 #pragma clang diagnostic push
538 #pragma clang diagnostic ignored "-Wundeclared-selector"
539         [sender doCommandBySelector:@selector(noop:)]; // AppKit sends noop: if the ime does not handle an event
540 #pragma clang diagnostic pop
541     }
542
543     inputMethodView = nil;
544     return YES;
545 }
546
547 @end
548
549 #endif // !PLATFORM(IOS)