summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTor Lillqvist <tml@collabora.com>2020-08-24 12:03:40 +0300
committerTor Lillqvist <tml@collabora.com>2020-08-25 13:41:20 +0200
commit2a8db1b5abe937698d2dc08c70c38d31512bb822 (patch)
treea702bc8c833470ca82abc28094ce0a00b6c0fa35
parentf6e7ec9ee137f5a3b5dc21d5e452ea3e78d11832 (diff)
Use CollaboraOnlineWebViewKeyboardManager
For now, just copy its source files here. When/if I figure out what is the appropriate way to package that framework for use in other products (like the Collabora Office iOS app) I will use that instead. Change-Id: If808f96b6a72c80e54dc84fce80a551503c96335 Reviewed-on: https://gerrit.libreoffice.org/c/online/+/101268 Tested-by: Jenkins Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice@gmail.com> Reviewed-by: Tor Lillqvist <tml@collabora.com>
-rw-r--r--ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.h28
-rw-r--r--ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.m240
-rw-r--r--ios/Mobile.xcodeproj/project.pbxproj6
-rw-r--r--ios/Mobile/DocumentViewController.mm5
4 files changed, 279 insertions, 0 deletions
diff --git a/ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.h b/ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.h
new file mode 100644
index 000000000..c62f54c48
--- /dev/null
+++ b/ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.h
@@ -0,0 +1,28 @@
+// -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*-
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import <WebKit/WebKit.h>
+
+@interface CollaboraOnlineWebViewKeyboardManager : NSObject
+
+/**
+ * @param webView The WKWebView that displays Collabora Online's loleaflet.html. Will not do
+ * anything useful for WKWebViews not displaying that. The loleaflet.html can be in an arbitrarily
+ * deeply nested iframe.
+ */
+- (nonnull CollaboraOnlineWebViewKeyboardManager *)initForWebView:(nonnull WKWebView *)webView;
+
+@end
+
+// vim:set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.m b/ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.m
new file mode 100644
index 000000000..0e7d3208f
--- /dev/null
+++ b/ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.m
@@ -0,0 +1,240 @@
+// -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*-
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import <CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.h>
+
+@interface _COWVKMKeyInputControl : UITextView<UITextViewDelegate> {
+ WKWebView *webView;
+}
+
+- (instancetype)initForWebView:(nonnull WKWebView *)webView;
+
+@end
+
+@implementation _COWVKMKeyInputControl
+
+- (instancetype)initForWebView:(nonnull WKWebView *)webView {
+ self = [super init];
+
+ self->webView = webView;
+ self.delegate = self;
+
+ return self;
+}
+
+- (void)postMessage:(NSString *)message {
+
+ NSMutableString *js = [NSMutableString string];
+
+ [js appendString:@""
+ "{"
+ " const message = "];
+
+ [js appendString:message];
+
+ // We check if window.COKbdMgrCallback is a function, and in that case call that directly.
+ // Otherwise we iterate over iframes and post a message that the event listener that we install
+ // will receive and handle, and recurse.
+
+ [js appendString:@";"
+ " if (typeof window.COKbdMgrCallback === 'function') {"
+ " window.COKbdMgrCallback(message);"
+ " } else {"
+ " const iframes = document.getElementsByTagName('iframe');"
+ " for (let i = 0; i < iframes.length; i++) {"
+ " iframes[i].contentWindow.postMessage(message, '*');"
+ " };"
+ " }"
+ "}"];
+
+ [webView evaluateJavaScript:js
+ completionHandler:^(id _Nullable obj, NSError *_Nullable error) {
+ if (error) {
+ if (error.userInfo[@"WKJavaScriptExceptionMessage"])
+ NSLog(@"Error when executing JavaScript: %@: %@", error.localizedDescription, error.userInfo[@"WKJavaScriptExceptionMessage"]);
+ else
+ NSLog(@"Error when executing JavaScript: %@", error.localizedDescription);
+ }
+ }];
+}
+
+- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
+ NSLog(@"COKbdMgr: shouldChangeTextInRange({%lu, %lu}, '%@')", (unsigned long)range.location, (unsigned long)range.length, text);
+ NSLog(@"self.text is now length %lu '%@'", self.text.length, self.text);
+
+ NSMutableString *quotedText = [NSMutableString string];
+
+ for (unsigned i = 0; i < text.length; i++) {
+ const unichar c = [text characterAtIndex:i];
+ if (c == '\'' || c == '\\') {
+ [quotedText appendString:@"\\"];
+ [quotedText appendFormat:@"%c", c];
+ } else if (c < ' ' || c >= 0x7F) {
+ [quotedText appendFormat:@"\\u%04X", c];
+ } else {
+ [quotedText appendFormat:@"%c", c];
+ }
+ }
+
+ NSMutableString *message = [NSMutableString string];
+
+ [message appendFormat:@"{id: 'COKbdMgr', command: 'replaceText', location: %lu, length: %lu, text: '", range.location, range.length];
+ [message appendString:quotedText];
+ [message appendString:@"'}"];
+
+ [self postMessage:message];
+
+ return YES;
+}
+
+- (BOOL)canBecomeFirstResponder {
+ return YES;
+}
+
+@synthesize hasText;
+
+@end
+
+@interface CollaboraOnlineWebViewKeyboardManager () <WKScriptMessageHandler> {
+ WKWebView *webView;
+ _COWVKMKeyInputControl *control;
+}
+
+@end
+
+@implementation CollaboraOnlineWebViewKeyboardManager
+
+- (CollaboraOnlineWebViewKeyboardManager *)initForWebView:(nonnull WKWebView *)webView {
+ self->webView = webView;
+
+ [webView.configuration.userContentController
+ addScriptMessageHandler:self
+ name:@"CollaboraOnlineWebViewKeyboardManager"];
+
+ NSString *script = @"window.addEventListener('message', function(event) {"
+ " if (event.data.id === 'COKbdMgr') {"
+ " if (typeof window.COKbdMgrCallback === 'function') {"
+ " window.COKbdMgrCallback(event.data);"
+ " } else {"
+ " const iframes = document.getElementsByTagName('iframe');"
+ " for (let i = 0; i < iframes.length; i++) {"
+ " iframes[i].contentWindow.postMessage(event.data, '*');"
+ " };"
+ " }"
+ " }"
+ "});";
+
+ WKUserScript *userScript = [[WKUserScript alloc] initWithSource:script
+ injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
+ forMainFrameOnly:NO];
+
+ [webView.configuration.userContentController addUserScript:userScript];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(keyboardDidHide:)
+ name:UIKeyboardDidHideNotification
+ object:nil];
+
+ return self;
+}
+
+- (void)displayKeyboardOfType:(NSString *)type withText:(NSString *)text at:(NSUInteger)location {
+ if (control == nil) {
+ control = [[_COWVKMKeyInputControl alloc] initForWebView:self->webView];
+ if (type != nil) {
+ UIKeyboardType keyboardType = UIKeyboardTypeDefault;
+ if ([type caseInsensitiveCompare:@"default"] == NSOrderedSame)
+ ;
+ else if ([type caseInsensitiveCompare:@"asciicapable"] == NSOrderedSame)
+ keyboardType = UIKeyboardTypeASCIICapable;
+ else if ([type caseInsensitiveCompare:@"numbersandpunctuation"] == NSOrderedSame)
+ keyboardType = UIKeyboardTypeNumbersAndPunctuation;
+ else if ([type caseInsensitiveCompare:@"url"] == NSOrderedSame)
+ keyboardType = UIKeyboardTypeURL;
+ else if ([type caseInsensitiveCompare:@"numberpad"] == NSOrderedSame)
+ keyboardType = UIKeyboardTypeNumberPad;
+ else if ([type caseInsensitiveCompare:@"phonepad"] == NSOrderedSame)
+ keyboardType = UIKeyboardTypePhonePad;
+ else if ([type caseInsensitiveCompare:@"namephonepad"] == NSOrderedSame)
+ keyboardType = UIKeyboardTypeNamePhonePad;
+ else if ([type caseInsensitiveCompare:@"emailaddress"] == NSOrderedSame)
+ keyboardType = UIKeyboardTypeEmailAddress;
+ else if ([type caseInsensitiveCompare:@"decimalpad"] == NSOrderedSame)
+ keyboardType = UIKeyboardTypeDecimalPad;
+ else if ([type caseInsensitiveCompare:@"asciicapablenumberpad"] == NSOrderedSame)
+ keyboardType = UIKeyboardTypeASCIICapableNumberPad;
+ else if ([type caseInsensitiveCompare:@"alphabet"] == NSOrderedSame)
+ keyboardType = UIKeyboardTypeAlphabet;
+ else
+ NSLog(@"COKbdMgr: Unrecognized keyboard type %@", type);
+ if (keyboardType != UIKeyboardTypeDefault)
+ control.keyboardType = keyboardType;
+ }
+ // Don't auto-capitalize start of input as we have no idea about the context into which it
+ // will be added.
+ control.autocapitalizationType = UITextAutocapitalizationTypeNone;
+
+ control.text = text;
+ control.selectedRange = NSMakeRange(location, 0);
+
+ [self->webView addSubview:control];
+ // NSLog(@"COKbdMgr: added _COWVKMKeyInputControl to webView");
+ [control becomeFirstResponder];
+ }
+}
+
+- (void)hideKeyboard {
+ if (control != nil) {
+ [control removeFromSuperview];
+ // NSLog(@"COKbdMgr: removed _COWVKMKeyInputControl from webView");
+ control = nil;
+ }
+}
+
+- (void)userContentController:(nonnull WKUserContentController *)userContentController
+ didReceiveScriptMessage:(nonnull WKScriptMessage *)message {
+ if (![message.name isEqualToString:@"CollaboraOnlineWebViewKeyboardManager"]) {
+ NSLog(@"Received unrecognized script message name: %@ %@", message.name, message.body);
+ return;
+ }
+
+ if ([message.body isKindOfClass:[NSDictionary class]]) {
+ NSString *stringCommand = message.body[@"command"];
+ if ([stringCommand isEqualToString:@"display"]) {
+ NSString *type = message.body[@"type"];
+ NSString *text = message.body[@"text"];
+ NSNumber *location = message.body[@"location"];
+ [self displayKeyboardOfType:type withText:text at:(location != nil ? [location unsignedIntegerValue] : UINT_MAX)];
+ } else if ([stringCommand isEqualToString:@"hide"]) {
+ [self hideKeyboard];
+ } else if (stringCommand == nil) {
+ NSLog(@"No 'command' in %@", message.body);
+ } else {
+ NSLog(@"Received unrecognized command:%@", stringCommand);
+ }
+ } else {
+ NSLog(@"Received unrecognized message body of type %@: %@, should be a dictionary (JS object)", [message.body class], message.body);
+ }
+}
+
+- (void)keyboardDidHide:(NSNotification *)notification {
+ if (control != nil) {
+ [control removeFromSuperview];
+ control = nil;
+ }
+}
+
+@end
+
+// vim:set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/ios/Mobile.xcodeproj/project.pbxproj b/ios/Mobile.xcodeproj/project.pbxproj
index c2a5396d9..d020a0b95 100644
--- a/ios/Mobile.xcodeproj/project.pbxproj
+++ b/ios/Mobile.xcodeproj/project.pbxproj
@@ -15,6 +15,7 @@
BE00F8B5213ED543001CE2D4 /* libiconv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = BE00F8B4213ED543001CE2D4 /* libiconv.tbd */; };
BE00F8B7213ED573001CE2D4 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = BE00F8B6213ED573001CE2D4 /* libz.tbd */; };
BE18C7DE226DE09A001AD27E /* Branding in Resources */ = {isa = PBXBuildFile; fileRef = BE18C7DD226DE09A001AD27E /* Branding */; };
+ BE2FB29E24F3B146006E18B1 /* CollaboraOnlineWebViewKeyboardManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BE2FB29D24F3B146006E18B1 /* CollaboraOnlineWebViewKeyboardManager.m */; };
BE5EB5C1213FE29900E0826C /* Log.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BE5EB5B9213FE29900E0826C /* Log.cpp */; };
BE5EB5C2213FE29900E0826C /* SpookyV2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BE5EB5BA213FE29900E0826C /* SpookyV2.cpp */; };
BE5EB5C3213FE29900E0826C /* Session.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BE5EB5BB213FE29900E0826C /* Session.cpp */; };
@@ -106,6 +107,8 @@
BE28F896228CE04700C00C48 /* langselect.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = langselect.cxx; path = "../../ios-device/desktop/source/app/langselect.cxx"; sourceTree = "<group>"; };
BE28F897228CE04700C00C48 /* lockfile2.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = lockfile2.cxx; path = "../../ios-device/desktop/source/app/lockfile2.cxx"; sourceTree = "<group>"; };
BE28F898228CE04700C00C48 /* userinstall.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = userinstall.cxx; path = "../../ios-device/desktop/source/app/userinstall.cxx"; sourceTree = "<group>"; };
+ BE2FB29C24F3B146006E18B1 /* CollaboraOnlineWebViewKeyboardManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CollaboraOnlineWebViewKeyboardManager.h; path = CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.h; sourceTree = SOURCE_ROOT; };
+ BE2FB29D24F3B146006E18B1 /* CollaboraOnlineWebViewKeyboardManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CollaboraOnlineWebViewKeyboardManager.m; path = CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.m; sourceTree = SOURCE_ROOT; };
BE34D10F218B66B600815297 /* docsh.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = docsh.cxx; path = "../../ios-device/sw/source/uibase/app/docsh.cxx"; sourceTree = "<group>"; };
BE34D110218B66B600815297 /* docstyle.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = docstyle.cxx; path = "../../ios-device/sw/source/uibase/app/docstyle.cxx"; sourceTree = "<group>"; };
BE34D111218B66B600815297 /* docshdrw.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = docshdrw.cxx; path = "../../ios-device/sw/source/uibase/app/docshdrw.cxx"; sourceTree = "<group>"; };
@@ -2433,6 +2436,8 @@
BE8D77292136762500AC58EA /* Mobile */ = {
isa = PBXGroup;
children = (
+ BE2FB29C24F3B146006E18B1 /* CollaboraOnlineWebViewKeyboardManager.h */,
+ BE2FB29D24F3B146006E18B1 /* CollaboraOnlineWebViewKeyboardManager.m */,
BE58E13021874A2E00249358 /* Mobile.entitlements */,
BE5EB5D92140363100E0826C /* ios.mm */,
BE00F8922139494E001CE2D4 /* Resources */,
@@ -3416,6 +3421,7 @@
BE5EB5C7213FE29900E0826C /* Protocol.cpp in Sources */,
BE8D772F2136762500AC58EA /* DocumentBrowserViewController.mm in Sources */,
BE5EB5D0213FE2D000E0826C /* TileCache.cpp in Sources */,
+ BE2FB29E24F3B146006E18B1 /* CollaboraOnlineWebViewKeyboardManager.m in Sources */,
BE5EB5C5213FE29900E0826C /* MessageQueue.cpp in Sources */,
BE7228E22417BC9F000ADABD /* StringVector.cpp in Sources */,
BE5EB5D621401E0F00E0826C /* Storage.cpp in Sources */,
diff --git a/ios/Mobile/DocumentViewController.mm b/ios/Mobile/DocumentViewController.mm
index 2467bbfbb..2a2d870c3 100644
--- a/ios/Mobile/DocumentViewController.mm
+++ b/ios/Mobile/DocumentViewController.mm
@@ -19,6 +19,7 @@
#import <sys/stat.h>
#import "ios.h"
+#import "CollaboraOnlineWebViewKeyboardManager.h"
#import "FakeSocket.hpp"
#import "LOOLWSD.hpp"
#import "Log.hpp"
@@ -31,6 +32,7 @@
@interface DocumentViewController() <WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler, UIScrollViewDelegate, UIDocumentPickerDelegate> {
int closeNotificationPipeForForwardingThread[2];
NSURL *downloadAsTmpURL;
+ CollaboraOnlineWebViewKeyboardManager *keyboardManager;
}
@end
@@ -100,6 +102,9 @@ static IMP standardImpOfInputAccessoryView = nil;
// contents is handled fully in JavaScript, the WebView has no knowledge of that.)
self.webView.scrollView.delegate = self;
+ keyboardManager =
+ [[CollaboraOnlineWebViewKeyboardManager alloc] initForWebView:self.webView];
+
[self.view addSubview:self.webView];
self.webView.navigationDelegate = self;