[Libreoffice-commits] online.git: Branch 'distro/collabora/co-4-2' - ios/CollaboraOnlineWebViewKeyboardManager ios/Mobile ios/Mobile.xcodeproj

Tor Lillqvist (via logerrit) logerrit at kemper.freedesktop.org
Wed Sep 2 17:13:27 UTC 2020


 ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.h |   28 +
 ios/CollaboraOnlineWebViewKeyboardManager/CollaboraOnlineWebViewKeyboardManager.m |  240 ++++++++++
 ios/Mobile.xcodeproj/project.pbxproj                                              |    6 
 ios/Mobile/DocumentViewController.mm                                              |    5 
 4 files changed, 279 insertions(+)

New commits:
commit 391c1bbebac005e7e6e5ca1ab706ab946f117f2e
Author:     Tor Lillqvist <tml at collabora.com>
AuthorDate: Mon Aug 24 12:03:40 2020 +0300
Commit:     Tor Lillqvist <tml at collabora.com>
CommitDate: Wed Sep 2 19:13:08 2020 +0200

    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 at gmail.com>
    Reviewed-by: Tor Lillqvist <tml at collabora.com>
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/101951

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>
+
+ at 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;
+
+ at 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>
+
+ at interface _COWVKMKeyInputControl : UITextView<UITextViewDelegate> {
+    WKWebView *webView;
+}
+
+- (instancetype)initForWebView:(nonnull WKWebView *)webView;
+
+ at end
+
+ at 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;
+}
+
+ at synthesize hasText;
+
+ at end
+
+ at interface CollaboraOnlineWebViewKeyboardManager () <WKScriptMessageHandler> {
+    WKWebView *webView;
+    _COWVKMKeyInputControl *control;
+}
+
+ at end
+
+ at 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;
+    }
+}
+
+ at end
+
+// vim:set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/ios/Mobile.xcodeproj/project.pbxproj b/ios/Mobile.xcodeproj/project.pbxproj
index bedc02c3b..68966947d 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 */; };
@@ -103,6 +104,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>"; };
@@ -2263,6 +2266,8 @@
 		BE8D77292136762500AC58EA /* Mobile */ = {
 			isa = PBXGroup;
 			children = (
+				BE2FB29C24F3B146006E18B1 /* CollaboraOnlineWebViewKeyboardManager.h */,
+				BE2FB29D24F3B146006E18B1 /* CollaboraOnlineWebViewKeyboardManager.m */,
 				BE58E13021874A2E00249358 /* Mobile.entitlements */,
 				BE5EB5D92140363100E0826C /* ios.mm */,
 				BE00F8922139494E001CE2D4 /* Resources */,
@@ -3048,6 +3053,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 c94161c02..cefd23c9e 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"
@@ -32,6 +33,7 @@ static DocumentViewController* theSingleton = nil;
 @interface DocumentViewController() <WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler, UIScrollViewDelegate, UIDocumentPickerDelegate> {
     int closeNotificationPipeForForwardingThread[2];
     NSURL *downloadAsTmpURL;
+    CollaboraOnlineWebViewKeyboardManager *keyboardManager;
 }
 
 @end
@@ -103,6 +105,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;


More information about the Libreoffice-commits mailing list