blob: bb283aaf6e761a6a62be6d41fc51b0b86b103bcc [file] [log] [blame]
/*
* Copyright (C) 2005, 2007, 2016 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "TextInputController.h"
#if PLATFORM(MAC)
#import "DumpRenderTreeMac.h"
#import <AppKit/NSInputManager.h>
#import <AppKit/NSTextAlternatives.h>
#import <WebKit/WebDocument.h>
#import <WebKit/WebFrame.h>
#import <WebKit/WebFramePrivate.h>
#import <WebKit/WebFrameView.h>
#import <WebKit/WebHTMLViewPrivate.h>
#import <WebKit/WebScriptObject.h>
#import <WebKit/WebView.h>
#import <WebKit/WebViewPrivate.h>
#import <pal/spi/mac/NSTextInputContextSPI.h>
@interface TextInputController (DumpRenderTreeInputMethodHandler)
- (BOOL)interpretKeyEvents:(NSArray *)eventArray withSender:(WebHTMLView *)sender;
@end
@interface WebHTMLView (DumpRenderTreeInputMethodHandler)
- (void)interpretKeyEvents:(NSArray *)eventArray;
@end
@interface WebHTMLView (WebKitSecretsTextInputControllerIsAwareOf)
- (WebFrame *)_frame;
- (NSAttributedString *)_attributedStringFromDOMRange:(DOMRange *)range;
- (NSAttributedString *)_legacyAttributedStringFrom:(DOMNode*)startContainer offset:(int)startOffset to:(DOMNode*)endContainer offset:(int)endOffset;
@end
@implementation WebHTMLView (DumpRenderTreeInputMethodHandler)
- (void)interpretKeyEvents:(NSArray *)eventArray
{
WebScriptObject *obj = [[self _frame] windowObject];
TextInputController *tic = [obj valueForKey:@"textInputController"];
if (![tic interpretKeyEvents:eventArray withSender:self])
[super interpretKeyEvents:eventArray];
}
@end
@interface WebNSRange : NSObject {
@private
NSRange _range;
}
- (id)initWithNSRange:(NSRange)range;
- (unsigned)location;
- (unsigned)length;
@end
@implementation WebNSRange
- (id)initWithNSRange:(NSRange)range
{
self = [super init];
if (!self)
return self;
_range = range;
return self;
}
- (unsigned)location
{
return _range.location;
}
- (unsigned)length
{
return _range.length;
}
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)selector
{
return !(selector == @selector(location) || selector == @selector(length));
}
@end
@implementation NSMutableAttributedString (TextInputController)
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
{
if (aSelector == @selector(string)
|| aSelector == @selector(getLength)
|| aSelector == @selector(ranges)
|| aSelector == @selector(attributeNamesAtIndex:)
|| aSelector == @selector(valueOfAttribute:atIndex:)
|| aSelector == @selector(addAttribute:value:)
|| aSelector == @selector(addAttribute:value:from:length:)
|| aSelector == @selector(addColorAttribute:red:green:blue:alpha:)
|| aSelector == @selector(addColorAttribute:red:green:blue:alpha:from:length:)
|| aSelector == @selector(addFontAttribute:fontName:size:)
|| aSelector == @selector(addFontAttribute:fontName:size:from:length:))
return NO;
return YES;
}
+ (NSString *)webScriptNameForSelector:(SEL)aSelector
{
if (aSelector == @selector(getLength))
return @"length";
if (aSelector == @selector(ranges))
return @"ranges";
if (aSelector == @selector(attributeNamesAtIndex:))
return @"getAttributeNamesAtIndex";
if (aSelector == @selector(valueOfAttribute:atIndex:))
return @"getAttributeValueAtIndex";
if (aSelector == @selector(addAttribute:value:))
return @"addAttribute";
if (aSelector == @selector(addAttribute:value:from:length:))
return @"addAttributeForRange";
if (aSelector == @selector(addColorAttribute:red:green:blue:alpha:))
return @"addColorAttribute";
if (aSelector == @selector(addColorAttribute:red:green:blue:alpha:from:length:))
return @"addColorAttributeForRange";
if (aSelector == @selector(addFontAttribute:fontName:size:))
return @"addFontAttribute";
if (aSelector == @selector(addFontAttribute:fontName:size:from:length:))
return @"addFontAttributeForRange";
return nil;
}
- (int)getLength
{
return (int)[self length];
}
- (NSArray *)ranges
{
NSMutableArray *array = [NSMutableArray array];
[self enumerateAttributesInRange:NSMakeRange(0, [self length]) options:0 usingBlock:^(NSDictionary *attributes, NSRange range, BOOL *stop) {
WebNSRange *webRange = [[WebNSRange alloc] initWithNSRange:range];
[array addObject:webRange];
[webRange release];
}];
return array;
}
- (NSArray *)attributeNamesAtIndex:(int)index
{
NSDictionary *attributes = [self attributesAtIndex:(unsigned)index effectiveRange:nil];
return [attributes allKeys];
}
- (id)valueOfAttribute:(NSString *)attrName atIndex:(int)index
{
return [self attribute:attrName atIndex:(unsigned)index effectiveRange:nil];
}
- (void)addAttribute:(NSString *)attrName value:(id)value
{
[self addAttribute:attrName value:value range:NSMakeRange(0, [self length])];
}
- (void)addAttribute:(NSString *)attrName value:(id)value from:(int)from length:(int)length
{
[self addAttribute:attrName value:value range:NSMakeRange((unsigned)from, (unsigned)length)];
}
- (void)addColorAttribute:(NSString *)attrName red:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
{
[self addAttribute:attrName value:[NSColor colorWithDeviceRed:red green:green blue:blue alpha:alpha] range:NSMakeRange(0, [self length])];
}
- (void)addColorAttribute:(NSString *)attrName red:(float)red green:(float)green blue:(float)blue alpha:(float)alpha from:(int)from length:(int)length
{
[self addAttribute:attrName value:[NSColor colorWithDeviceRed:red green:green blue:blue alpha:alpha] range:NSMakeRange((unsigned)from, (unsigned)length)];
}
- (void)addFontAttribute:(NSString *)attrName fontName:(NSString *)fontName size:(float)fontSize
{
[self addAttribute:attrName value:[NSFont fontWithName:fontName size:fontSize] range:NSMakeRange(0, [self length])];
}
- (void)addFontAttribute:(NSString *)attrName fontName:(NSString *)fontName size:(float)fontSize from:(int)from length:(int)length
{
[self addAttribute:attrName value:[NSFont fontWithName:fontName size:fontSize] range:NSMakeRange((unsigned)from, (unsigned)length)];
}
@end
@implementation TextInputController
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
{
if (aSelector == @selector(insertText:)
|| aSelector == @selector(doCommand:)
|| aSelector == @selector(setMarkedText:selectedFrom:length:suppressUnderline:highlights:)
|| aSelector == @selector(unmarkText)
|| aSelector == @selector(hasMarkedText)
|| aSelector == @selector(conversationIdentifier)
|| aSelector == @selector(substringFrom:length:)
|| aSelector == @selector(attributedSubstringFrom:length:)
|| aSelector == @selector(legacyAttributedString:offset:to:offset:)
|| aSelector == @selector(markedRange)
|| aSelector == @selector(selectedRange)
|| aSelector == @selector(firstRectForCharactersFrom:length:)
|| aSelector == @selector(characterIndexForPointX:Y:)
|| aSelector == @selector(validAttributesForMarkedText)
|| aSelector == @selector(attributedStringForTyping)
|| aSelector == @selector(attributedStringWithString:)
|| aSelector == @selector(setInputMethodHandler:)
|| aSelector == @selector(dictatedStringWithPrimaryString:alternative:alternativeOffset:alternativeLength:)
|| aSelector == @selector(stringWithUndoGroupingInsertion:))
return NO;
return YES;
}
+ (NSString *)webScriptNameForSelector:(SEL)aSelector
{
if (aSelector == @selector(insertText:))
return @"insertText";
if (aSelector == @selector(doCommand:))
return @"doCommand";
if (aSelector == @selector(setMarkedText:selectedFrom:length:suppressUnderline:highlights:))
return @"setMarkedText";
if (aSelector == @selector(substringFrom:length:))
return @"substringFromRange";
if (aSelector == @selector(attributedSubstringFrom:length:))
return @"attributedSubstringFromRange";
if (aSelector == @selector(legacyAttributedString:offset:to:offset:))
return @"legacyAttributedString";
if (aSelector == @selector(firstRectForCharactersFrom:length:))
return @"firstRectForCharacterRange";
if (aSelector == @selector(characterIndexForPointX:Y:))
return @"characterIndexForPoint";
if (aSelector == @selector(attributedStringWithString:))
return @"makeAttributedString"; // just a factory method, doesn't call into NSTextInput
if (aSelector == @selector(setInputMethodHandler:))
return @"setInputMethodHandler";
if (aSelector == @selector(dictatedStringWithPrimaryString:alternative:alternativeOffset:alternativeLength:))
return @"makeDictatedString";
if (aSelector == @selector(stringWithUndoGroupingInsertion:))
return @"makeUndoGroupingInsertionString";
return nil;
}
- (id)initWithWebView:(WebView *)wv
{
self = [super init];
webView = wv;
inputMethodView = nil;
inputMethodHandler = nil;
return self;
}
- (void)dealloc
{
[inputMethodHandler release];
inputMethodHandler = nil;
[super dealloc];
}
- (WebFrame *)selectedOrMainFrame
{
return webView.selectedFrame ?: webView.mainFrame;
}
- (NSObject <NSTextInput> *)textInput
{
NSView <NSTextInput> *view = inputMethodView ?: (id)self.selectedOrMainFrame.frameView.documentView;
return [view conformsToProtocol:@protocol(NSTextInput)] ? view : nil;
}
- (void)insertText:(id)aString
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput)
[textInput insertText:aString];
}
- (void)doCommand:(NSString *)aCommand
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput)
[textInput doCommandBySelector:NSSelectorFromString(aCommand)];
}
- (void)setMarkedText:(NSString *)aString selectedFrom:(int)from length:(int)length suppressUnderline:(BOOL)suppressUnderline highlights:(NSArray<NSDictionary *> *)highlights
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput)
[textInput setMarkedText:aString selectedRange:NSMakeRange(from, length)];
}
- (void)unmarkText
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput)
[textInput unmarkText];
}
- (BOOL)hasMarkedText
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput)
return [textInput hasMarkedText];
return FALSE;
}
- (long)conversationIdentifier
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput)
return [textInput conversationIdentifier];
return 0;
}
- (NSString *)substringFrom:(int)from length:(int)length
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput)
return [[textInput attributedSubstringFromRange:NSMakeRange(from, length)] string];
return @"";
}
- (NSMutableAttributedString *)attributedSubstringFrom:(int)from length:(int)length
{
NSObject <NSTextInput> *textInput = [self textInput];
NSMutableAttributedString *ret = [[[NSMutableAttributedString alloc] init] autorelease];
if (textInput)
[ret setAttributedString:[textInput attributedSubstringFromRange:NSMakeRange(from, length)]];
return ret;
}
- (NSAttributedString *)legacyAttributedString:(DOMNode*)startContainer offset:(int)startOffset to:(DOMNode*)endContainer offset:(int)endOffset
{
id documentView = [[[webView mainFrame] frameView] documentView];
if (![documentView isKindOfClass:[WebHTMLView class]])
return nil;
return [(WebHTMLView *)documentView _legacyAttributedStringFrom:startContainer offset:startOffset to:endContainer offset:endOffset];
}
- (NSArray *)markedRange
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput) {
NSRange range = [textInput markedRange];
return @[@(range.location), @(range.length)];
}
return nil;
}
- (NSArray *)selectedRange
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput) {
NSRange range = [textInput selectedRange];
return @[@(range.location), @(range.length)];
}
return nil;
}
- (NSArray *)firstRectForCharactersFrom:(int)from length:(int)length
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput) {
NSRect rect = [textInput firstRectForCharacterRange:NSMakeRange(from, length)];
if (rect.origin.x || rect.origin.y || rect.size.width || rect.size.height) {
rect.origin = [[webView window] convertScreenToBase:rect.origin];
rect = [webView convertRect:rect fromView:nil];
}
return @[ @(rect.origin.x), @(rect.origin.y), @(rect.size.width), @(rect.size.height) ];
}
return nil;
}
- (NSInteger)characterIndexForPointX:(float)x Y:(float)y
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput) {
NSPoint point = NSMakePoint(x, y);
point = [webView convertPoint:point toView:nil];
point = [[webView window] convertBaseToScreen:point];
NSInteger index = [textInput characterIndexForPoint:point];
if (index == NSNotFound)
return -1;
return index;
}
return 0;
}
- (NSArray *)validAttributesForMarkedText
{
NSObject <NSTextInput> *textInput = [self textInput];
if (textInput)
return [textInput validAttributesForMarkedText];
return nil;
}
- (NSMutableAttributedString *)attributedStringForTyping
{
// The string has to be non-empty.
return [[[NSMutableAttributedString alloc] initWithString:@" " attributes:[webView typingAttributes]] autorelease];
}
- (NSMutableAttributedString *)attributedStringWithString:(NSString *)string
{
return [[[NSMutableAttributedString alloc] initWithString:string] autorelease];
}
- (NSMutableAttributedString *)stringWithUndoGroupingInsertion:(NSString *)string
{
NSMutableAttributedString* attributedString = [self dictatedStringWithPrimaryString:string alternative:@"test" alternativeOffset:0 alternativeLength:1];
[attributedString addAttribute:NSTextInsertionUndoableAttributeName value:@YES range:NSMakeRange(0, [attributedString length])];
return attributedString;
}
- (NSMutableAttributedString*)dictatedStringWithPrimaryString:(NSString*)aString alternative:(NSString*)alternative alternativeOffset:(int)offset alternativeLength:(int)length
{
NSMutableAttributedString* dictatedString = [self attributedStringWithString:aString];
NSRange rangeWithAlternative = NSMakeRange((NSUInteger)offset, (NSUInteger)length);
NSString* subStringWithAlternative = [aString substringWithRange:rangeWithAlternative];
if (!subStringWithAlternative)
return nil;
NSTextAlternatives* alternativeObject = [[[NSTextAlternatives alloc] initWithPrimaryString:subStringWithAlternative alternativeStrings:@[alternative]] autorelease];
if (!alternativeObject)
return nil;
[dictatedString addAttribute:NSTextAlternativesAttributeName value:alternativeObject range:rangeWithAlternative];
return dictatedString;
}
- (void)setInputMethodHandler:(WebScriptObject *)handler
{
if (inputMethodHandler == handler)
return;
[handler retain];
[inputMethodHandler release];
inputMethodHandler = handler;
}
- (BOOL)interpretKeyEvents:(NSArray *)eventArray withSender:(WebHTMLView *)sender
{
if (!inputMethodHandler)
return NO;
inputMethodView = sender;
NSEvent *event = [eventArray objectAtIndex:0];
unsigned modifierFlags = [event modifierFlags];
NSMutableArray *modifiers = [[NSMutableArray alloc] init];
if (modifierFlags & NSEventModifierFlagCapsLock)
[modifiers addObject:@"NSAlphaShiftKeyMask"];
if (modifierFlags & NSEventModifierFlagShift)
[modifiers addObject:@"NSShiftKeyMask"];
if (modifierFlags & NSEventModifierFlagControl)
[modifiers addObject:@"NSControlKeyMask"];
if (modifierFlags & NSEventModifierFlagOption)
[modifiers addObject:@"NSAlternateKeyMask"];
if (modifierFlags & NSEventModifierFlagCommand)
[modifiers addObject:@"NSCommandKeyMask"];
if (modifierFlags & NSEventModifierFlagNumericPad)
[modifiers addObject:@"NSNumericPadKeyMask"];
if (modifierFlags & NSEventModifierFlagHelp)
[modifiers addObject:@"NSHelpKeyMask"];
if (modifierFlags & NSEventModifierFlagFunction)
[modifiers addObject:@"NSFunctionKeyMask"];
WebScriptObject* eventParam = [inputMethodHandler evaluateWebScript:@"new Object();"];
[eventParam setValue:[event characters] forKey:@"characters"];
[eventParam setValue:[event charactersIgnoringModifiers] forKey:@"charactersIgnoringModifiers"];
[eventParam setValue:[NSNumber numberWithBool:[event isARepeat]] forKey:@"isARepeat"];
[eventParam setValue:[NSNumber numberWithUnsignedShort:[event keyCode]] forKey:@"keyCode"];
[eventParam setValue:modifiers forKey:@"modifierFlags"];
[modifiers release];
id result = [inputMethodHandler callWebScriptMethod:@"call" withArguments:@[inputMethodHandler, eventParam]];
if (![result respondsToSelector:@selector(boolValue)] || ![result boolValue]) {
IGNORE_WARNINGS_BEGIN("undeclared-selector")
[sender doCommandBySelector:@selector(noop:)]; // AppKit sends noop: if the ime does not handle an event
IGNORE_WARNINGS_END
}
inputMethodView = nil;
return YES;
}
@end
#endif // PLATFORM(MAC)