[Libreoffice-commits] online.git: android/lib common/MobileApp.cpp common/MobileApp.hpp common/SigUtil.cpp common/SigUtil.hpp ios/config.h.in ios/ios.h ios/ios.mm ios/Mobile ios/Mobile.xcodeproj kit/ChildSession.cpp kit/ChildSession.hpp kit/Kit.cpp kit/Kit.hpp Makefile.am net/FakeSocket.cpp test/WhiteBoxTests.cpp wsd/DocumentBroker.cpp wsd/DocumentBroker.hpp wsd/LOOLWSD.cpp wsd/LOOLWSD.hpp

Tor Lillqvist (via logerrit) logerrit at kemper.freedesktop.org
Fri Jun 26 11:10:12 UTC 2020


 Makefile.am                             |    2 
 android/lib/src/main/cpp/androidapp.cpp |    4 
 common/MobileApp.cpp                    |   46 +++++++
 common/MobileApp.hpp                    |   57 ++++++++
 common/SigUtil.cpp                      |   14 --
 common/SigUtil.hpp                      |   25 ++-
 ios/Mobile.xcodeproj/project.pbxproj    |   12 +
 ios/Mobile/AppDelegate.mm               |   32 ++--
 ios/Mobile/CODocument.h                 |    1 
 ios/Mobile/CODocument.mm                |   12 +
 ios/Mobile/DocumentViewController.h     |    1 
 ios/Mobile/DocumentViewController.mm    |   48 ++-----
 ios/Mobile/Info.plist.in                |   19 ++
 ios/Mobile/SceneDelegate.h              |   18 ++
 ios/Mobile/SceneDelegate.m              |   20 +++
 ios/config.h.in                         |    4 
 ios/ios.h                               |    3 
 ios/ios.mm                              |    4 
 kit/ChildSession.cpp                    |   12 -
 kit/ChildSession.hpp                    |    2 
 kit/Kit.cpp                             |  209 +++++++++++++++++++++++++++-----
 kit/Kit.hpp                             |    7 -
 net/FakeSocket.cpp                      |    5 
 test/WhiteBoxTests.cpp                  |    5 
 wsd/DocumentBroker.cpp                  |   23 +--
 wsd/DocumentBroker.hpp                  |    7 -
 wsd/LOOLWSD.cpp                         |   59 ++++++---
 wsd/LOOLWSD.hpp                         |    4 
 28 files changed, 503 insertions(+), 152 deletions(-)

New commits:
commit 7f25109f72738706359a63c9062764699f00f568
Author:     Tor Lillqvist <tml at collabora.com>
AuthorDate: Fri Apr 24 10:46:54 2020 +0300
Commit:     Tor Lillqvist <tml at collabora.com>
CommitDate: Fri Jun 26 13:09:51 2020 +0200

    tdf#128502: Chunk of work to enable "multi-tasking" in the iOS app
    
    Seems to not cause any serious regressions in the iOS app or in "make
    run", but of course I am not able to run a comprehensive check of all
    functionality.
    
    Change-Id: I44a0e8d60bdbc0a885db88475961575c5e95ce88
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/93037
    Tested-by: Jenkins
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Tor Lillqvist <tml at collabora.com>

diff --git a/Makefile.am b/Makefile.am
index ca868c8e7..171a07e36 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -93,6 +93,7 @@ shared_sources = common/FileUtil.cpp \
                  common/Session.cpp \
                  common/Seccomp.cpp \
                  common/MessageQueue.cpp \
+                 common/MobileApp.cpp \
                  common/SigUtil.cpp \
                  common/SpookyV2.cpp \
                  common/Unit.cpp \
@@ -250,6 +251,7 @@ shared_headers = common/Common.hpp \
                  common/Authorization.hpp \
                  common/MessageQueue.hpp \
                  common/Message.hpp \
+                 common/MobileApp.hpp \
                  common/Png.hpp \
                  common/Rectangle.hpp \
                  common/SigUtil.hpp \
diff --git a/android/lib/src/main/cpp/androidapp.cpp b/android/lib/src/main/cpp/androidapp.cpp
index a3f847135..7570f96ca 100644
--- a/android/lib/src/main/cpp/androidapp.cpp
+++ b/android/lib/src/main/cpp/androidapp.cpp
@@ -225,10 +225,6 @@ Java_org_libreoffice_androidlib_LOActivity_postMobileMessageNative(JNIEnv *env,
                                        // is saved by closing it.
                                        fakeSocketClose(closeNotificationPipeForForwardingThread[1]);
 
-                                       // Flag to make the inter-thread plumbing in the Online
-                                       // bits go away quicker.
-                                       MobileTerminationFlag = true;
-
                                        // Close our end of the fake socket connection to the
                                        // ClientSession thread, so that it terminates
                                        fakeSocketClose(currentFakeClientFd);
diff --git a/common/MobileApp.cpp b/common/MobileApp.cpp
new file mode 100644
index 000000000..15706216b
--- /dev/null
+++ b/common/MobileApp.cpp
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <cassert>
+#include <map>
+#include <mutex>
+
+#include "MobileApp.hpp"
+
+#if MOBILEAPP
+
+static std::map<unsigned, DocumentData> idToDocDataMap;
+static std::mutex idToDocDataMapMutex;
+
+DocumentData &allocateDocumentDataForMobileAppDocId(unsigned docId)
+{
+    const std::lock_guard<std::mutex> lock(idToDocDataMapMutex);
+
+    assert(idToDocDataMap.find(docId) == idToDocDataMap.end());
+    idToDocDataMap[docId] = DocumentData();
+    return idToDocDataMap[docId];
+}
+
+DocumentData &getDocumentDataForMobileAppDocId(unsigned docId)
+{
+    const std::lock_guard<std::mutex> lock(idToDocDataMapMutex);
+
+    assert(idToDocDataMap.find(docId) != idToDocDataMap.end());
+    return idToDocDataMap[docId];
+}
+
+void deallocateDocumentDataForMobileAppDocId(unsigned docId)
+{
+    assert(idToDocDataMap.find(docId) != idToDocDataMap.end());
+    idToDocDataMap.erase(docId);
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/common/MobileApp.hpp b/common/MobileApp.hpp
new file mode 100644
index 000000000..fc816113c
--- /dev/null
+++ b/common/MobileApp.hpp
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include "config.h"
+
+#if MOBILEAPP
+
+#include <LibreOfficeKit/LibreOfficeKit.hxx>
+
+#ifdef IOS
+#import "CODocument.h"
+#endif
+
+// On iOS at least we want to be able to have several documents open in the same app process.
+
+// It is somewhat complicated to make sure we access the same LibreOfficeKit object for the document
+// in both the iOS-specific Objective-C++ code and in the mostly generic Online C++ code.
+
+// We pass around a numeric ever-increasing document identifier that gets biumped for each document
+// the system asks the app to open.
+
+// For iOS, it is the static std::atomic<unsigned> appDocIdCounter in CODocument.mm.
+
+// In practice it will probably be equivalent to the DocumentBroker::DocBrokerId or the number that
+// the core SfxViewShell::GetDocId() returns, but there might be situations where multi-threading
+// and opening of several documents in sequence very quickly might cause discrepancies, so it is
+// better to usea different counter to be sure. Patches to use just one counter welcome.
+
+struct DocumentData
+{
+    lok::Document *loKitDocument;
+#ifdef IOS
+    CODocument *coDocument;
+#endif
+
+    DocumentData() :
+        loKitDocument(nullptr)
+#ifdef IOS
+        , coDocument(nil)
+#endif
+    {
+    }
+};
+
+DocumentData &allocateDocumentDataForMobileAppDocId(unsigned docId);
+DocumentData &getDocumentDataForMobileAppDocId(unsigned docId);
+void deallocateDocumentDataForMobileAppDocId(unsigned docId);
+
+#endif
diff --git a/common/SigUtil.cpp b/common/SigUtil.cpp
index 4891010db..dff88b8a6 100644
--- a/common/SigUtil.cpp
+++ b/common/SigUtil.cpp
@@ -38,14 +38,10 @@
 #include "Common.hpp"
 #include "Log.hpp"
 
+#ifndef IOS
 static std::atomic<bool> TerminationFlag(false);
 static std::atomic<bool> DumpGlobalState(false);
-#if MOBILEAPP
-std::atomic<bool> MobileTerminationFlag(false);
-#else
-// Mobile defines its own, which is constexpr.
 static std::atomic<bool> ShutdownRequestFlag(false);
-#endif
 
 namespace SigUtil
 {
@@ -71,6 +67,7 @@ namespace SigUtil
     }
 #endif
 
+#if !MOBILEAPP
     bool getDumpGlobalState()
     {
         return DumpGlobalState;
@@ -80,11 +77,7 @@ namespace SigUtil
     {
         DumpGlobalState = false;
     }
-}
 
-#if !MOBILEAPP
-namespace SigUtil
-{
     /// This traps the signal-handler so we don't _Exit
     /// while dumping stack trace. It's re-entrant.
     /// Used to safely increment and decrement the signal-handler trap.
@@ -384,8 +377,9 @@ namespace SigUtil
 
         return false;
     }
+#endif // !MOBILEAPP
 }
 
-#endif // !MOBILEAPP
+#endif // !IOS
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/common/SigUtil.hpp b/common/SigUtil.hpp
index d3eaf9726..9cc5b69d4 100644
--- a/common/SigUtil.hpp
+++ b/common/SigUtil.hpp
@@ -13,13 +13,9 @@
 #include <mutex>
 #include <signal.h>
 
-#if MOBILEAPP
-static constexpr bool ShutdownRequestFlag(false);
-extern std::atomic<bool> MobileTerminationFlag;
-#endif
-
 namespace SigUtil
 {
+#ifndef IOS
     /// Get the flag used to commence clean shutdown.
     /// requestShutdown() is used to set the flag.
     bool getShutdownRequestFlag();
@@ -33,14 +29,29 @@ namespace SigUtil
     /// Only necessary in Mobile.
     void resetTerminationFlag();
 #endif
+#else
+    // In the mobile apps we have no need to shut down the app.
+    inline constexpr bool getShutdownRequestFlag()
+    {
+        return false;
+    }
+
+    inline constexpr bool getTerminationFlag()
+    {
+        return false;
+    }
+
+    inline void setTerminationFlag()
+    {
+    }
+#endif
 
+#if !MOBILEAPP
     /// Get the flag to dump internal state.
     bool getDumpGlobalState();
     /// Reset the flag to dump internal state.
     void resetDumpGlobalState();
 
-#if !MOBILEAPP
-
     /// Wait for the signal handler, if any,
     /// and prevent _Exit while collecting backtrace.
     void waitSigHandlerTrap();
diff --git a/ios/Mobile.xcodeproj/project.pbxproj b/ios/Mobile.xcodeproj/project.pbxproj
index bedc02c3b..fbdb3e17c 100644
--- a/ios/Mobile.xcodeproj/project.pbxproj
+++ b/ios/Mobile.xcodeproj/project.pbxproj
@@ -66,6 +66,8 @@
 		BEBF3EB0246EB1C800415E87 /* RequestDetails.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BEBF3EAF246EB1C800415E87 /* RequestDetails.cpp */; };
 		BECD984024336DD400016117 /* device-mobile.css in Resources */ = {isa = PBXBuildFile; fileRef = BECD983E24336DD400016117 /* device-mobile.css */; };
 		BECD984124336DD400016117 /* device-tablet.css in Resources */ = {isa = PBXBuildFile; fileRef = BECD983F24336DD400016117 /* device-tablet.css */; };
+		BEDCC84E2452F82800FB02BD /* MobileApp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BEDCC84C2452F82800FB02BD /* MobileApp.cpp */; };
+		BEDCC8992456FFAD00FB02BD /* SceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = BEDCC8982456FFAC00FB02BD /* SceneDelegate.m */; };
 		BEFB1EE121C29CC70081D757 /* L10n.mm in Sources */ = {isa = PBXBuildFile; fileRef = BEFB1EE021C29CC70081D757 /* L10n.mm */; };
 /* End PBXBuildFile section */
 
@@ -1176,6 +1178,10 @@
 		BECBD41423D9C98500DA5582 /* svddrgmt.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = svddrgmt.cxx; path = "../../ios-device/svx/source/svdraw/svddrgmt.cxx"; sourceTree = "<group>"; };
 		BECD983E24336DD400016117 /* device-mobile.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = "device-mobile.css"; path = "../../../loleaflet/dist/device-mobile.css"; sourceTree = "<group>"; };
 		BECD983F24336DD400016117 /* device-tablet.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = "device-tablet.css"; path = "../../../loleaflet/dist/device-tablet.css"; sourceTree = "<group>"; };
+		BEDCC84C2452F82800FB02BD /* MobileApp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MobileApp.cpp; sourceTree = "<group>"; };
+		BEDCC84D2452F82800FB02BD /* MobileApp.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MobileApp.hpp; sourceTree = "<group>"; };
+		BEDCC8972456FFAC00FB02BD /* SceneDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SceneDelegate.h; sourceTree = "<group>"; };
+		BEDCC8982456FFAC00FB02BD /* SceneDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SceneDelegate.m; sourceTree = "<group>"; };
 		BEDCC943246175E100FB02BD /* sessionlistener.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = sessionlistener.cxx; path = "../../ios-device/framework/source/services/sessionlistener.cxx"; sourceTree = "<group>"; };
 		BEDCC944246175E100FB02BD /* substitutepathvars.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = substitutepathvars.cxx; path = "../../ios-device/framework/source/services/substitutepathvars.cxx"; sourceTree = "<group>"; };
 		BEDCC945246175E100FB02BD /* pathsettings.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = pathsettings.cxx; path = "../../ios-device/framework/source/services/pathsettings.cxx"; sourceTree = "<group>"; };
@@ -1885,6 +1891,8 @@
 				BE58E129217F295B00249358 /* Log.hpp */,
 				BE5EB5BD213FE29900E0826C /* MessageQueue.cpp */,
 				BE58E12D217F295B00249358 /* MessageQueue.hpp */,
+				BEDCC84C2452F82800FB02BD /* MobileApp.cpp */,
+				BEDCC84D2452F82800FB02BD /* MobileApp.hpp */,
 				BE58E12A217F295B00249358 /* Png.hpp */,
 				BE5EB5BF213FE29900E0826C /* Protocol.cpp */,
 				BE58E12E217F295B00249358 /* Protocol.hpp */,
@@ -2276,6 +2284,8 @@
 				BE8D77312136762500AC58EA /* DocumentViewController.mm */,
 				BEFB1EDF21C29CC70081D757 /* L10n.h */,
 				BEFB1EE021C29CC70081D757 /* L10n.mm */,
+				BEDCC8972456FFAC00FB02BD /* SceneDelegate.h */,
+				BEDCC8982456FFAC00FB02BD /* SceneDelegate.m */,
 				BE80E45C21B6CEF100859C97 /* TemplateSectionHeaderView.h */,
 				BE80E45D21B6CEF200859C97 /* TemplateSectionHeaderView.m */,
 				BE80E45621B68F5000859C97 /* TemplateCollectionViewController.h */,
@@ -3032,8 +3042,10 @@
 				BE5EB5C3213FE29900E0826C /* Session.cpp in Sources */,
 				BE5EB5D22140039100E0826C /* LOOLWSD.cpp in Sources */,
 				BEFB1EE121C29CC70081D757 /* L10n.mm in Sources */,
+				BEDCC8992456FFAD00FB02BD /* SceneDelegate.m in Sources */,
 				BE5EB5C6213FE29900E0826C /* SigUtil.cpp in Sources */,
 				BE5EB5C1213FE29900E0826C /* Log.cpp in Sources */,
+				BEDCC84E2452F82800FB02BD /* MobileApp.cpp in Sources */,
 				BE5EB5DA2140363100E0826C /* ios.mm in Sources */,
 				BE5EB5D421400DC100E0826C /* DocumentBroker.cpp in Sources */,
 				BEA28377214FFD8C00848631 /* Unit.cpp in Sources */,
diff --git a/ios/Mobile/AppDelegate.mm b/ios/Mobile/AppDelegate.mm
index b76158806..9e5f19278 100644
--- a/ios/Mobile/AppDelegate.mm
+++ b/ios/Mobile/AppDelegate.mm
@@ -30,8 +30,6 @@
 #import "LOOLWSD.hpp"
 #import "Util.hpp"
 
-static LOOLWSD *loolwsd = nullptr;
-
 NSString *app_locale;
 
 static void download(NSURL *source, NSURL *destination) {
@@ -206,6 +204,9 @@ static void updateTemplates(NSData *data, NSURLResponse *response)
 
     comphelper::LibreOfficeKit::setLanguageTag(LanguageTag(OUString::fromUtf8(OString([app_locale UTF8String])), true));
 
+    // This fires off a thread running the LOKit runLoop()
+    runKitLoopInAThread();
+
     // Look for the setting indicating the URL for a file containing a list of URLs for template
     // documents to download. If set, start a task to download it, and then to download the listed
     // templates.
@@ -247,26 +248,30 @@ static void updateTemplates(NSData *data, NSURLResponse *response)
 
     fakeSocketSetLoggingCallback([](const std::string& line)
                                  {
-                                     LOG_TRC_NOFILE(line);
+                                     LOG_INF(line);
                                  });
 
     dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
                    ^{
-                       assert(loolwsd == nullptr);
                        char *argv[2];
                        argv[0] = strdup([[NSBundle mainBundle].executablePath UTF8String]);
                        argv[1] = nullptr;
                        Util::setThreadName("app");
-                       while (true) {
-                           loolwsd = new LOOLWSD();
-                           loolwsd->run(1, argv);
-                           delete loolwsd;
-                           LOG_TRC("One run of LOOLWSD completed");
-                       }
+                       auto loolwsd = new LOOLWSD();
+                       loolwsd->run(1, argv);
+
+                       // Should never return
+                       assert(false);
+                       NSLog(@"lolwsd->run() unexpectedly returned");
+                       std::abort();
                    });
     return YES;
 }
 
+- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options API_AVAILABLE(ios(13.0)) {
+    return [UISceneConfiguration configurationWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
+}
+
 - (void)applicationWillResignActive:(UIApplication *)application {
 }
 
@@ -285,17 +290,14 @@ static void updateTemplates(NSData *data, NSURLResponse *response)
     std::_Exit(1);
 }
 
+// This method is called when you use the "Share > Open in Collabora Office" functionality in the
+// Files app. Possibly also in other use cases.
 - (BOOL)application:(UIApplication *)app openURL:(NSURL *)inputURL options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
     // Ensure the URL is a file URL
     if (!inputURL.isFileURL) {
         return NO;
     }
 
-    // If we already have a document open, close it
-    if (lok_document != nullptr && [DocumentViewController singleton] != nil) {
-        [[DocumentViewController singleton] bye];
-    }
-
     // Reveal / import the document at the URL
     DocumentBrowserViewController *documentBrowserViewController = (DocumentBrowserViewController *)self.window.rootViewController;
     [documentBrowserViewController revealDocumentAtURL:inputURL importIfNeeded:YES completion:^(NSURL * _Nullable revealedDocumentURL, NSError * _Nullable error) {
diff --git a/ios/Mobile/CODocument.h b/ios/Mobile/CODocument.h
index 79f772a1b..7aa262c25 100644
--- a/ios/Mobile/CODocument.h
+++ b/ios/Mobile/CODocument.h
@@ -19,6 +19,7 @@
 @public
     int fakeClientFd;
     NSURL *copyFileURL;
+    unsigned appDocId;
 }
 
 @property (weak) DocumentViewController *viewController;
diff --git a/ios/Mobile/CODocument.mm b/ios/Mobile/CODocument.mm
index 7eaa9c90f..c12bd2e91 100644
--- a/ios/Mobile/CODocument.mm
+++ b/ios/Mobile/CODocument.mm
@@ -32,6 +32,7 @@
 #import "KitHelper.hpp"
 #import "Log.hpp"
 #import "LOOLWSD.hpp"
+#import "MobileApp.hpp"
 #import "Protocol.hpp"
 
 @implementation CODocument
@@ -40,6 +41,12 @@
     return [NSData dataWithContentsOfFile:[copyFileURL path] options:0 error:errorPtr];
 }
 
+// We keep a running count of opening documents here. This is not necessarily in sync with the
+// DocBrokerId in DocumentBroker due to potential parallelism when opening multiple documents in
+// quick succession.
+
+static std::atomic<unsigned> appDocIdCounter(1);
+
 - (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)errorPtr {
 
     // If this method is called a second time on the same CODocument object, just ignore it. This
@@ -59,10 +66,13 @@
 
     NSURL *url = [[NSBundle mainBundle] URLForResource:@"loleaflet" withExtension:@"html"];
     NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
+    appDocId = appDocIdCounter++;
+    allocateDocumentDataForMobileAppDocId(appDocId).coDocument = self;
     components.queryItems = @[ [NSURLQueryItem queryItemWithName:@"file_path" value:[copyFileURL absoluteString]],
                                [NSURLQueryItem queryItemWithName:@"closebutton" value:@"1"],
                                [NSURLQueryItem queryItemWithName:@"permission" value:@"edit"],
-                               [NSURLQueryItem queryItemWithName:@"lang" value:app_locale]
+                               [NSURLQueryItem queryItemWithName:@"lang" value:app_locale],
+                               [NSURLQueryItem queryItemWithName:@"appdocid" value:[NSString stringWithFormat:@"%u", appDocId]],
                              ];
 
     NSURLRequest *request = [[NSURLRequest alloc]initWithURL:components.URL];
diff --git a/ios/Mobile/DocumentViewController.h b/ios/Mobile/DocumentViewController.h
index 297255999..cb845b302 100644
--- a/ios/Mobile/DocumentViewController.h
+++ b/ios/Mobile/DocumentViewController.h
@@ -20,7 +20,6 @@
 @property (strong) NSURL *slideshowURL;
 
 - (void)bye;
-+ (DocumentViewController*)singleton;
 
 @end
 
diff --git a/ios/Mobile/DocumentViewController.mm b/ios/Mobile/DocumentViewController.mm
index c94161c02..3f8744fe4 100644
--- a/ios/Mobile/DocumentViewController.mm
+++ b/ios/Mobile/DocumentViewController.mm
@@ -22,13 +22,12 @@
 #import "FakeSocket.hpp"
 #import "LOOLWSD.hpp"
 #import "Log.hpp"
+#import "MobileApp.hpp"
 #import "SigUtil.hpp"
 #import "Util.hpp"
 
 #import "DocumentViewController.h"
 
-static DocumentViewController* theSingleton = nil;
-
 @interface DocumentViewController() <WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler, UIScrollViewDelegate, UIDocumentPickerDelegate> {
     int closeNotificationPipeForForwardingThread[2];
     NSURL *downloadAsTmpURL;
@@ -79,8 +78,6 @@ static IMP standardImpOfInputAccessoryView = nil;
 - (void)viewDidLoad {
     [super viewDidLoad];
 
-    theSingleton = self;
-
     WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
     WKUserContentController *userContentController = [[WKUserContentController alloc] init];
 
@@ -317,10 +314,6 @@ static IMP standardImpOfInputAccessoryView = nil;
                                            // is saved by closing it.
                                            fakeSocketClose(self->closeNotificationPipeForForwardingThread[1]);
 
-                                           // Flag to make the inter-thread plumbing in the Online
-                                           // bits go away quicker.
-                                           MobileTerminationFlag = true;
-
                                            // Close our end of the fake socket connection to the
                                            // ClientSession thread, so that it terminates
                                            fakeSocketClose(self.document->fakeClientFd);
@@ -348,13 +341,14 @@ static IMP standardImpOfInputAccessoryView = nil;
                                assert(false);
                            });
 
-            // First we simply send it the URL. This corresponds to the GET request with Upgrade to
-            // WebSocket.
+            // First we simply send the Online C++ parts the URL and the appDocId. This corresponds
+            // to the GET request with Upgrade to WebSocket.
             std::string url([[self.document->copyFileURL absoluteString] UTF8String]);
             p.fd = self.document->fakeClientFd;
             p.events = POLLOUT;
             fakeSocketPoll(&p, 1, -1);
-            fakeSocketWrite(self.document->fakeClientFd, url.c_str(), url.size());
+            std::string message(url + " " + std::to_string(self.document->appDocId));
+            fakeSocketWrite(self.document->fakeClientFd, message.c_str(), message.size());
 
             return;
         } else if ([message.body isEqualToString:@"BYE"]) {
@@ -369,7 +363,7 @@ static IMP standardImpOfInputAccessoryView = nil;
             self.slideshowFile = Util::createRandomTmpDir() + "/slideshow.svg";
             self.slideshowURL = [NSURL fileURLWithPath:[NSString stringWithUTF8String:self.slideshowFile.c_str()] isDirectory:NO];
 
-            lok_document->saveAs([[self.slideshowURL absoluteString] UTF8String], "svg", nullptr);
+            getDocumentDataForMobileAppDocId(self.document->appDocId).loKitDocument->saveAs([[self.slideshowURL absoluteString] UTF8String], "svg", nullptr);
 
             // Add a new full-screen WebView displaying the slideshow.
 
@@ -423,7 +417,7 @@ static IMP standardImpOfInputAccessoryView = nil;
 
             std::string printFile = Util::createRandomTmpDir() + "/print.pdf";
             NSURL *printURL = [NSURL fileURLWithPath:[NSString stringWithUTF8String:printFile.c_str()] isDirectory:NO];
-            lok_document->saveAs([[printURL absoluteString] UTF8String], "pdf", nullptr);
+            getDocumentDataForMobileAppDocId(self.document->appDocId).loKitDocument->saveAs([[printURL absoluteString] UTF8String], "pdf", nullptr);
 
             UIPrintInteractionController *pic = [UIPrintInteractionController sharedPrintController];
             UIPrintInfo *printInfo = [UIPrintInfo printInfo];
@@ -476,7 +470,7 @@ static IMP standardImpOfInputAccessoryView = nil;
 
                 std::remove([[downloadAsTmpURL path] UTF8String]);
 
-                lok_document->saveAs([[downloadAsTmpURL absoluteString] UTF8String], [format UTF8String], nullptr);
+                getDocumentDataForMobileAppDocId(self.document->appDocId).loKitDocument->saveAs([[downloadAsTmpURL absoluteString] UTF8String], [format UTF8String], nullptr);
 
                 // Then verify that it indeed was saved, and then use an
                 // UIDocumentPickerViewController to ask the user where to store the exported
@@ -526,31 +520,17 @@ static IMP standardImpOfInputAccessoryView = nil;
     // Close one end of the socket pair, that will wake up the forwarding thread above
     fakeSocketClose(closeNotificationPipeForForwardingThread[0]);
 
-    // We can't wait for the LOOLWSD::lokit_main_mutex directly here because this is called on the
-    // main queue and the main queue must be ready to execute code dispatched by the system APIs
-    // used to do document saving.
-    dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
-                   ^{
-                       // Wait for lokit_main thread to exit
-                       std::lock_guard<std::mutex> lock(LOOLWSD::lokit_main_mutex);
-
-                       theSingleton = nil;
+    // deallocateDocumentDataForMobileAppDocId(self.document->appDocId);
 
-                       [[NSFileManager defaultManager] removeItemAtURL:self.document->copyFileURL error:nil];
+    [[NSFileManager defaultManager] removeItemAtURL:self.document->copyFileURL error:nil];
 
-                       // And only then let the document browsing view show up again. The
-                       // dismissViewControllerAnimated must be done on the main queue.
-                       dispatch_async(dispatch_get_main_queue(),
-                                      ^{
-                                          [self dismissDocumentViewController];
-                                      });
+    // The dismissViewControllerAnimated must be done on the main queue.
+    dispatch_async(dispatch_get_main_queue(),
+                   ^{
+                       [self dismissDocumentViewController];
                    });
 }
 
-+ (DocumentViewController*)singleton {
-    return theSingleton;
-}
-
 @end
 
 // vim:set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/ios/Mobile/Info.plist.in b/ios/Mobile/Info.plist.in
index 54d68f36c..eb4b08e9b 100644
--- a/ios/Mobile/Info.plist.in
+++ b/ios/Mobile/Info.plist.in
@@ -421,6 +421,25 @@
 		<string>share/fonts/truetype/opens___.ttf</string>
 		@IOSAPP_FONTS@
 	</array>
+	<key>UIApplicationSceneManifest</key>
+	<dict>
+		<key>UIApplicationSupportsMultipleScenes</key>
+		<true/>
+		<key>UISceneConfigurations</key>
+		<dict>
+			<key>UIWindowSceneSessionRoleApplication</key>
+			<array>
+				<dict>
+					<key>UISceneDelegateClassName</key>
+					<string>SceneDelegate</string>
+					<key>UISceneConfigurationName</key>
+					<string>Default Configuration</string>
+					<key>UISceneStoryboardFile</key>
+					<string>Main</string>
+				</dict>
+			</array>
+		</dict>
+	</dict>
 	<key>UIFileSharingEnabled</key>
 	<true/>
 	<key>UILaunchStoryboardName</key>
diff --git a/ios/Mobile/SceneDelegate.h b/ios/Mobile/SceneDelegate.h
new file mode 100644
index 000000000..d0c8f4d8a
--- /dev/null
+++ b/ios/Mobile/SceneDelegate.h
@@ -0,0 +1,18 @@
+// -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*-
+//
+// This file is part of the LibreOffice project.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#import <UIKit/UIKit.h>
+
+ at interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
+
+ at property (strong, nonatomic) UIWindow * window;
+
+ at end
+
+// vim:set shiftwidth=4 softtabstop=4 expandtab:
+
diff --git a/ios/Mobile/SceneDelegate.m b/ios/Mobile/SceneDelegate.m
new file mode 100644
index 000000000..b403d7d6d
--- /dev/null
+++ b/ios/Mobile/SceneDelegate.m
@@ -0,0 +1,20 @@
+// -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*-
+//
+// This file is part of the LibreOffice project.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#import <UIKit/UIKit.h>
+
+#import "SceneDelegate.h"
+
+ at implementation SceneDelegate
+
+// Nothing needed so far, the window property in the .h file is enough.
+
+ at end
+
+// vim:set shiftwidth=4 softtabstop=4 expandtab:
+
diff --git a/ios/config.h.in b/ios/config.h.in
index 18ec2613d..05934c203 100644
--- a/ios/config.h.in
+++ b/ios/config.h.in
@@ -89,10 +89,10 @@
 #undef LT_OBJDIR
 
 /* Limit the maximum number of open connections */
-#define MAX_CONNECTIONS 3
+#define MAX_CONNECTIONS 100
 
 /* Limit the maximum number of open documents */
-#define MAX_DOCUMENTS 1
+#define MAX_DOCUMENTS 100
 
 /* Define to 1 if this is a mobileapp (eg. Android) build. */
 #define MOBILEAPP 1
diff --git a/ios/ios.h b/ios/ios.h
index a4283cefb..198b9e90a 100644
--- a/ios/ios.h
+++ b/ios/ios.h
@@ -7,12 +7,9 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
-#include <mutex>
-
 #include <LibreOfficeKit/LibreOfficeKit.hxx>
 
 extern int loolwsd_server_socket_fd;
-extern lok::Document *lok_document;
 
 extern LibreOfficeKit *lo_kit;
 
diff --git a/ios/ios.mm b/ios/ios.mm
index 97c49c3e9..b5c6ac65b 100644
--- a/ios/ios.mm
+++ b/ios/ios.mm
@@ -6,8 +6,6 @@
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-#include <cstring>
-
 #include "ios.h"
 
 #import <Foundation/Foundation.h>
@@ -18,7 +16,7 @@ extern "C" {
 }
 
 int loolwsd_server_socket_fd = -1;
-lok::Document *lok_document = nullptr;
+
 LibreOfficeKit *lo_kit;
 
 // vim:set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/kit/ChildSession.cpp b/kit/ChildSession.cpp
index ca9701e9a..cc8f7413e 100644
--- a/kit/ChildSession.cpp
+++ b/kit/ChildSession.cpp
@@ -10,6 +10,7 @@
 #include <config.h>
 
 #include "ChildSession.hpp"
+#include "MobileApp.hpp"
 
 #include <climits>
 #include <fstream>
@@ -32,10 +33,6 @@
 #include <Poco/Net/AcceptCertificateHandler.h>
 #endif
 
-#ifdef IOS
-#import "DocumentViewController.h"
-#endif
-
 #include <common/FileUtil.hpp>
 #include <common/JsonUtil.hpp>
 #include <common/Authorization.hpp>
@@ -2486,11 +2483,10 @@ void ChildSession::loKitCallback(const int type, const std::string& payload)
 
             if (!commandName.isEmpty() && commandName.toString() == ".uno:Save" && !success.isEmpty() && success.toString() == "true")
             {
-                CODocument *document = [[DocumentViewController singleton] document];
-
+                CODocument *document = getDocumentDataForMobileAppDocId(_docManager->getMobileAppDocId()).coDocument;
                 [document saveToURL:[document fileURL]
-                 forSaveOperation:UIDocumentSaveForOverwriting
-                 completionHandler:^(BOOL success) {
+                   forSaveOperation:UIDocumentSaveForOverwriting
+                  completionHandler:^(BOOL success) {
                         LOG_TRC("ChildSession::loKitCallback() save completion handler gets " << (success?"YES":"NO"));
                     }];
             }
diff --git a/kit/ChildSession.hpp b/kit/ChildSession.hpp
index 4c4dbb2ec..48edf98b2 100644
--- a/kit/ChildSession.hpp
+++ b/kit/ChildSession.hpp
@@ -71,6 +71,8 @@ public:
     virtual bool sendFrame(const char* buffer, int length, WSOpCode opCode = WSOpCode::Text) = 0;
 
     virtual void alertAllUsers(const std::string& cmd, const std::string& kind) = 0;
+
+    virtual unsigned getMobileAppDocId() = 0;
 };
 
 struct RecordedEvent
diff --git a/kit/Kit.cpp b/kit/Kit.cpp
index 743251fef..269120e60 100644
--- a/kit/Kit.cpp
+++ b/kit/Kit.cpp
@@ -52,6 +52,7 @@
 
 #include "ChildSession.hpp"
 #include <Common.hpp>
+#include <MobileApp.hpp>
 #include <FileUtil.hpp>
 #include "KitHelper.hpp"
 #include "Kit.hpp"
@@ -768,7 +769,8 @@ public:
              const std::string& docId,
              const std::string& url,
              std::shared_ptr<TileQueue> tileQueue,
-             const std::shared_ptr<WebSocketHandler>& websocketHandler)
+             const std::shared_ptr<WebSocketHandler>& websocketHandler,
+             unsigned mobileAppDocId)
       : _loKit(loKit),
         _jailId(jailId),
         _docKey(docKey),
@@ -784,7 +786,8 @@ public:
         _stop(false),
         _isLoading(0),
         _editorId(-1),
-        _editorChangeWarning(false)
+        _editorChangeWarning(false),
+        _mobileAppDocId(mobileAppDocId)
     {
         LOG_INF("Document ctor for [" << _docKey <<
                 "] url [" << anonymizeUrl(_url) << "] on child [" << _jailId <<
@@ -809,6 +812,11 @@ public:
         {
             session.second->resetDocManager();
         }
+
+#ifdef IOS
+        deallocateDocumentDataForMobileAppDocId(_mobileAppDocId);
+#endif
+
     }
 
     const std::string& getUrl() const { return _url; }
@@ -1247,6 +1255,11 @@ public:
         alertAllUsers("errortoall: cmd=" + cmd + " kind=" + kind);
     }
 
+    unsigned getMobileAppDocId() override
+    {
+        return _mobileAppDocId;
+    }
+
     static void GlobalCallback(const int type, const char* p, void* data)
     {
         if (SigUtil::getTerminationFlag())
@@ -1465,9 +1478,6 @@ private:
 #endif
             LOG_INF("Document [" << anonymizeUrl(_url) << "] has no more views, but has " <<
                     _sessions.size() << " sessions still. Destroying the document.");
-#ifdef IOS
-            lok_document = nullptr;
-#endif
 #ifdef __ANDROID__
             _loKitDocumentForAndroidOnly.reset();
 #endif
@@ -1716,9 +1726,7 @@ private:
             const double totalTime = elapsed/1000.;
             LOG_DBG("Returned lokit::documentLoad(" << FileUtil::anonymizeUrl(pURL) << ") in " << totalTime << "ms.");
 #ifdef IOS
-            // The iOS app (and the Android one) has max one document open at a time, so we can keep
-            // a pointer to it in a global.
-            lok_document = _loKitDocument.get();
+            getDocumentDataForMobileAppDocId(_mobileAppDocId).loKitDocument = _loKitDocument.get();
 #endif
             if (!_loKitDocument || !_loKitDocument->get())
             {
@@ -2135,6 +2143,8 @@ private:
 #ifdef __ANDROID__
     friend std::shared_ptr<lok::Document> getLOKDocumentForAndroidOnly();
 #endif
+
+    const unsigned _mobileAppDocId;
 };
 
 #ifdef __ANDROID__
@@ -2153,10 +2163,42 @@ class KitSocketPoll : public SocketPoll
     std::chrono::steady_clock::time_point _pollEnd;
     std::shared_ptr<Document> _document;
 
-public:
     KitSocketPoll() :
         SocketPoll("kit")
     {
+#ifdef IOS
+        terminationFlag = false;
+#endif
+    }
+
+public:
+    ~KitSocketPoll()
+    {
+#ifdef IOS
+        std::unique_lock<std::mutex> lock(KSPollsMutex);
+        std::shared_ptr<KitSocketPoll> p;
+        auto i = KSPolls.begin();
+        for (; i != KSPolls.end(); ++i)
+        {
+            p = i->lock();
+            if (p && p.get() == this)
+                break;
+        }
+        assert(i != KSPolls.end());
+        KSPolls.erase(i);
+#endif
+    }
+
+    static std::shared_ptr<KitSocketPoll> create()
+    {
+        KitSocketPoll *p = new KitSocketPoll();
+        auto result = std::shared_ptr<KitSocketPoll>(p);
+#ifdef IOS
+        std::unique_lock<std::mutex> lock(KSPollsMutex);
+        KSPolls.push_back(result);
+        // KSPollsCV.notify_one();
+#endif
+        return result;
     }
 
     // process pending message-queue events.
@@ -2229,8 +2271,26 @@ public:
     {
         _document = std::move(document);
     }
+
+#ifdef IOS
+    static std::mutex KSPollsMutex;
+    // static std::condition_variable KSPollsCV;
+    static std::vector<std::weak_ptr<KitSocketPoll>> KSPolls;
+
+    std::mutex terminationMutex;
+    std::condition_variable terminationCV;
+    bool terminationFlag;
+#endif
 };
 
+#ifdef IOS
+
+std::mutex KitSocketPoll::KSPollsMutex;
+// std::condition_variable KitSocketPoll::KSPollsCV;
+std::vector<std::weak_ptr<KitSocketPoll>> KitSocketPoll::KSPolls;
+
+#endif
+
 class KitWebSocketHandler final : public WebSocketHandler
 {
     std::shared_ptr<TileQueue> _queue;
@@ -2238,16 +2298,18 @@ class KitWebSocketHandler final : public WebSocketHandler
     std::shared_ptr<lok::Office> _loKit;
     std::string _jailId;
     std::shared_ptr<Document> _document;
-    KitSocketPoll &_ksPoll;
+    std::shared_ptr<KitSocketPoll> _ksPoll;
+    const unsigned _mobileAppDocId;
 
 public:
-    KitWebSocketHandler(const std::string& socketName, const std::shared_ptr<lok::Office>& loKit, const std::string& jailId, KitSocketPoll& ksPoll) :
+    KitWebSocketHandler(const std::string& socketName, const std::shared_ptr<lok::Office>& loKit, const std::string& jailId, std::shared_ptr<KitSocketPoll> ksPoll, unsigned mobileAppDocId) :
         WebSocketHandler(/* isClient = */ true, /* isMasking */ false),
         _queue(std::make_shared<TileQueue>()),
         _socketName(socketName),
         _loKit(loKit),
         _jailId(jailId),
-        _ksPoll(ksPoll)
+        _ksPoll(ksPoll),
+        _mobileAppDocId(mobileAppDocId)
     {
     }
 
@@ -2296,14 +2358,16 @@ protected:
             std::string url;
             URI::decode(docKey, url);
             LOG_INF("New session [" << sessionId << "] request on url [" << url << "].");
+#ifndef IOS
             Util::setThreadName("kit_" + docId);
-
+#endif
             if (!_document)
             {
                 _document = std::make_shared<Document>(
                     _loKit, _jailId, docKey, docId, url, _queue,
-                    std::static_pointer_cast<WebSocketHandler>(shared_from_this()));
-                _ksPoll.setDocument(_document);
+                    std::static_pointer_cast<WebSocketHandler>(shared_from_this()),
+                    _mobileAppDocId);
+                _ksPoll->setDocument(_document);
             }
 
             // Validate and create session.
@@ -2319,9 +2383,16 @@ protected:
             LOG_INF("Terminating immediately due to parent 'exit' command.");
             Log::shutdown();
             std::_Exit(EX_SOFTWARE);
+#else
+#ifdef IOS
+            LOG_INF("Setting our KitSocketPoll's termination flag due to 'exit' command.");
+            std::unique_lock<std::mutex> lock(_ksPoll->terminationMutex);
+            _ksPoll->terminationFlag = true;
+            _ksPoll->terminationCV.notify_all();
 #else
             LOG_INF("Setting TerminationFlag due to 'exit' command.");
             SigUtil::setTerminationFlag();
+#endif
             _document.reset();
 #endif
         }
@@ -2359,6 +2430,11 @@ protected:
 #if !MOBILEAPP
         LOG_WRN("Kit connection lost without exit arriving from wsd. Setting TerminationFlag");
         SigUtil::setTerminationFlag();
+#endif
+#ifdef IOS
+        std::unique_lock<std::mutex> lock(_ksPoll->terminationMutex);
+        _ksPoll->terminationFlag = true;
+        _ksPoll->terminationCV.notify_all();
 #endif
     }
 };
@@ -2371,19 +2447,62 @@ void documentViewCallback(const int type, const char* payload, void* data)
 /// Called by LOK main-loop the central location for data processing.
 int pollCallback(void* pData, int timeoutUs)
 {
+#ifndef IOS
     if (!pData)
         return 0;
     else
         return reinterpret_cast<KitSocketPoll*>(pData)->kitPoll(timeoutUs);
+#else
+    std::unique_lock<std::mutex> lock(KitSocketPoll::KSPollsMutex);
+    if (KitSocketPoll::KSPolls.size() == 0)
+    {
+        // KitSocketPoll::KSPollsCV.wait(lock);
+        lock.unlock();
+        std::this_thread::sleep_for(std::chrono::microseconds(timeoutUs));
+    }
+    else
+    {
+        std::vector<std::shared_ptr<KitSocketPoll>> v;
+        for (const auto &i : KitSocketPoll::KSPolls)
+        {
+            auto p = i.lock();
+            if (p)
+                v.push_back(p);
+        }
+        lock.unlock();
+        for (const auto &p : v)
+            p->kitPoll(timeoutUs);
+    }
+
+    // We never want to exit the main loop
+    return 0;
+#endif
 }
 
 /// Called by LOK main-loop
 void wakeCallback(void* pData)
 {
+#ifndef IOS
     if (!pData)
         return;
     else
         return reinterpret_cast<KitSocketPoll*>(pData)->wakeup();
+#else
+    std::unique_lock<std::mutex> lock(KitSocketPoll::KSPollsMutex);
+    if (KitSocketPoll::KSPolls.size() == 0)
+        return;
+
+    std::vector<std::shared_ptr<KitSocketPoll>> v;
+    for (const auto &i : KitSocketPoll::KSPolls)
+    {
+        auto p = i.lock();
+        if (p)
+            v.push_back(p);
+    }
+    lock.unlock();
+    for (const auto &p : v)
+        p->wakeup();
+#endif
 }
 
 void setupKitEnvironment()
@@ -2435,10 +2554,9 @@ void lokit_main(
                 bool queryVersion,
                 bool displayVersion,
 #else
-                const std::string& documentUri,
                 int docBrokerSocket,
 #endif
-                size_t spareKitId
+                size_t numericIdentifier
                 )
 {
 #if !MOBILEAPP
@@ -2448,7 +2566,7 @@ void lokit_main(
     SigUtil::setTerminationSignals();
 #endif
 
-    Util::setThreadName("kit_spare_" + Util::encodeId(spareKitId, 3));
+    Util::setThreadName("kit_spare_" + Util::encodeId(numericIdentifier, 3));
 
     // Reinitialize logging when forked.
     const bool logToFile = std::getenv("LOOL_LOGFILE");
@@ -2497,8 +2615,6 @@ void lokit_main(
     std::shared_ptr<lok::Office> loKit;
     Path jailPath;
     ChildSession::NoCapsForKit = noCapabilities;
-#else
-    AnonymizeUserData = false;
 #endif // MOBILEAPP
 
     try
@@ -2699,8 +2815,11 @@ void lokit_main(
 
 #else // MOBILEAPP
 
-        // was not done by the preload
+#ifndef IOS
+        // Was not done by the preload.
+        // For iOS we call it in -[AppDelegate application: didFinishLaunchingWithOptions:]
         setupKitEnvironment();
+#endif
 
 #if defined(__linux) && !defined(__ANDROID__)
         Poco::URI userInstallationURI("file", LO_PATH);
@@ -2727,16 +2846,16 @@ void lokit_main(
 
 #endif // MOBILEAPP
 
-        KitSocketPoll mainKit;
-        mainKit.runOnClientThread(); // We will do the polling on this thread.
+        auto mainKit = KitSocketPoll::create();
+        mainKit->runOnClientThread(); // We will do the polling on this thread.
 
         std::shared_ptr<KitWebSocketHandler> websocketHandler =
-            std::make_shared<KitWebSocketHandler>("child_ws", loKit, jailId, mainKit);
+            std::make_shared<KitWebSocketHandler>("child_ws", loKit, jailId, mainKit, numericIdentifier);
 
 #if !MOBILEAPP
-        mainKit.insertNewUnixSocket(MasterLocation, pathAndQuery, websocketHandler, ProcSMapsFile);
+        mainKit->insertNewUnixSocket(MasterLocation, pathAndQuery, websocketHandler, ProcSMapsFile);
 #else
-        mainKit.insertNewFakeSocket(docBrokerSocket, websocketHandler);
+        mainKit->insertNewFakeSocket(docBrokerSocket, websocketHandler);
 #endif
 
         LOG_INF("New kit client websocket inserted.");
@@ -2749,6 +2868,7 @@ void lokit_main(
         }
 #endif
 
+#ifndef IOS
         if (!LIBREOFFICEKIT_HAS(kit, runLoop))
         {
             LOG_ERR("Kit is missing Unipoll API");
@@ -2758,7 +2878,7 @@ void lokit_main(
 
         LOG_INF("Kit unipoll loop run");
 
-        loKit->runLoop(pollCallback, wakeCallback, &mainKit);
+        loKit->runLoop(pollCallback, wakeCallback, mainKit.get());
 
         LOG_INF("Kit unipoll loop run terminated.");
 
@@ -2772,6 +2892,11 @@ void lokit_main(
 
         // Let forkit handle the jail cleanup.
 #endif
+
+#else // IOS
+        std::unique_lock<std::mutex> lock(mainKit->terminationMutex);
+        mainKit->terminationCV.wait(lock,[&]{ return mainKit->terminationFlag; } );
+#endif // !IOS
     }
     catch (const Exception& exc)
     {
@@ -2793,7 +2918,35 @@ void lokit_main(
 
 #endif
 }
-#endif
+
+#ifdef IOS
+
+// In the iOS app we can have several documents open in the app process at the same time, thus
+// several lokit_main() functions running at the same time. We want just one LO main loop, though,
+// so we start it separately in its own thread.
+
+void runKitLoopInAThread()
+{
+    std::thread([&]
+                {
+                    Util::setThreadName("lokit_runloop");
+
+                    std::shared_ptr<lok::Office> loKit = std::make_shared<lok::Office>(lo_kit);
+                    int dummy;
+                    loKit->runLoop(pollCallback, wakeCallback, &dummy);
+
+                    // Should never return
+                    assert(false);
+
+                    NSLog(@"loKit->runLoop() unexpectedly returned");
+
+                    std::abort();
+                }).detach();
+}
+
+#endif // IOS
+
+#endif // !BUILDING_TESTS
 
 std::string anonymizeUrl(const std::string& url)
 {
diff --git a/kit/Kit.hpp b/kit/Kit.hpp
index 1e60fdd3a..a5c6332b8 100644
--- a/kit/Kit.hpp
+++ b/kit/Kit.hpp
@@ -37,12 +37,15 @@ void lokit_main(
                 bool queryVersionInfo,
                 bool displayVersion,
 #else
-                const std::string& documentUri,
                 int docBrokerSocket,
 #endif
-                size_t spareKitId
+                size_t numericIdentifier
                 );
 
+#ifdef IOS
+void runKitLoopInAThread();
+#endif
+
 /// We need to get several env. vars right
 void setupKitEnvironment();
 
diff --git a/net/FakeSocket.cpp b/net/FakeSocket.cpp
index e8fd6bbca..a4ef761e5 100644
--- a/net/FakeSocket.cpp
+++ b/net/FakeSocket.cpp
@@ -257,6 +257,11 @@ static bool checkForPoll(std::vector<FakeSocketPair>& fds, struct pollfd *pollfd
                     retval = true;
                 }
             }
+            if (fds[pollfds[i].fd/2].shutdown[N])
+            {
+                    pollfds[i].revents |= POLLHUP;
+                    retval = true;
+            }
         }
     }
     return retval;
diff --git a/test/WhiteBoxTests.cpp b/test/WhiteBoxTests.cpp
index a2e676198..47023d2fa 100644
--- a/test/WhiteBoxTests.cpp
+++ b/test/WhiteBoxTests.cpp
@@ -653,6 +653,11 @@ public:
     void alertAllUsers(const std::string& /*cmd*/, const std::string& /*kind*/) override
     {
     }
+
+    unsigned getMobileAppDocId() override
+    {
+        return 0;
+    }
 };
 
 void WhiteBoxTests::testEmptyCellCursor()
diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp
index da71968f6..fe99ed26a 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -153,14 +153,6 @@ public:
 
     bool continuePolling() override
     {
-#if MOBILEAPP
-        if (MobileTerminationFlag)
-        {
-            LOG_TRC("Noticed MobileTerminationFlag.");
-            MobileTerminationFlag = false;
-            return false;
-        }
-#endif
         return TerminatingPoll::continuePolling();
     }
 
@@ -176,7 +168,8 @@ std::atomic<unsigned> DocumentBroker::DocBrokerId(1);
 DocumentBroker::DocumentBroker(ChildType type,
                                const std::string& uri,
                                const Poco::URI& uriPublic,
-                               const std::string& docKey) :
+                               const std::string& docKey,
+                               unsigned mobileAppDocId) :
     _limitLifeSeconds(0),
     _uriOrig(uri),
     _type(type),
@@ -200,11 +193,16 @@ DocumentBroker::DocumentBroker(ChildType type,
     _lockCtx(new LockContext()),
     _tileVersion(0),
     _debugRenderedTileCount(0),
-    _wopiLoadDuration(0)
+    _wopiLoadDuration(0),
+    _mobileAppDocId(mobileAppDocId)
 {
     assert(!_docKey.empty());
     assert(!LOOLWSD::ChildRoot.empty());
 
+#ifdef IOS
+    assert(_mobileAppDocId > 0);
+#endif
+
     LOG_INF("DocumentBroker [" << LOOLWSD::anonymizeUrl(_uriPublic.toString()) <<
             "] created with docKey [" << _docKey << ']');
 }
@@ -242,7 +240,7 @@ void DocumentBroker::pollThread()
     do
     {
         static const int timeoutMs = COMMAND_TIMEOUT_MS * 5;
-        _childProcess = getNewChild_Blocks(getPublicUri().getPath());
+        _childProcess = getNewChild_Blocks();
         if (_childProcess ||
             std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() -
                                                                   _threadStart).count() > timeoutMs)
@@ -253,7 +251,8 @@ void DocumentBroker::pollThread()
     }
     while (!_stop && _poll->continuePolling() && !SigUtil::getTerminationFlag() && !SigUtil::getShutdownRequestFlag());
 #else
-    _childProcess = getNewChild_Blocks(getPublicUri().getPath());
+    assert(_mobileAppDocId > 0);
+    _childProcess = getNewChild_Blocks(_mobileAppDocId);
 #endif
 
     if (!_childProcess)
diff --git a/wsd/DocumentBroker.hpp b/wsd/DocumentBroker.hpp
index 42d506629..4d09dff32 100644
--- a/wsd/DocumentBroker.hpp
+++ b/wsd/DocumentBroker.hpp
@@ -128,11 +128,11 @@ public:
     /// Dummy document broker that is marked to destroy.
     DocumentBroker();
 
-    /// Construct DocumentBroker with URI, docKey, and root path.
     DocumentBroker(ChildType type,
                    const std::string& uri,
                    const Poco::URI& uriPublic,
-                   const std::string& docKey);
+                   const std::string& docKey,
+                   unsigned mobileAppDocId = 0);
 
     virtual ~DocumentBroker();
 
@@ -439,6 +439,9 @@ private:
 
     /// Unique DocBroker ID for tracing and debugging.
     static std::atomic<unsigned> DocBrokerId;
+
+    // Relevant only in the mobile apps
+    const unsigned _mobileAppDocId;
 };
 
 #if !MOBILEAPP
diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp
index 9c713fbde..8cdf9974a 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -119,6 +119,7 @@ using Poco::Net::PartHandler;
 #  include <Kit.hpp>
 #endif
 #include <Log.hpp>
+#include <MobileApp.hpp>
 #include <Protocol.hpp>
 #include <Session.hpp>
 #if ENABLE_SSL
@@ -486,17 +487,20 @@ static size_t addNewChild(const std::shared_ptr<ChildProcess>& child)
 }
 
 #if MOBILEAPP
+#ifndef IOS
 std::mutex LOOLWSD::lokit_main_mutex;
 #endif
+#endif
 
-std::shared_ptr<ChildProcess> getNewChild_Blocks(const std::string& uri)
+std::shared_ptr<ChildProcess> getNewChild_Blocks(unsigned mobileAppDocId)
 {
     std::unique_lock<std::mutex> lock(NewChildrenMutex);
 
     const auto startTime = std::chrono::steady_clock::now();
 
 #if !MOBILEAPP
-    (void)uri;
+    (void) mobileAppDocId;
+
     LOG_DBG("getNewChild: Rebalancing children.");
     int numPreSpawn = LOOLWSD::NumPreSpawnedChildren;
     ++numPreSpawn; // Replace the one we'll dispatch just now.
@@ -519,12 +523,15 @@ std::shared_ptr<ChildProcess> getNewChild_Blocks(const std::string& uri)
 
     std::thread([&]
                 {
-                    std::lock_guard<std::mutex> lokit_main_lock(LOOLWSD::lokit_main_mutex);
+#ifndef IOS
+                    std::lock_guard<std::mutex> lock(LOOLWSD::lokit_main_mutex);
                     Util::setThreadName("lokit_main");
-
-                    // Ugly to have that static global, otoh we know there is just one LOOLWSD
-                    // object. (Even in real Online.)
-                    lokit_main(uri, LOOLWSD::prisonerServerSocketFD, 1);
+#else
+                    Util::setThreadName("lokit_main_" + Util::encodeId(mobileAppDocId, 3));
+#endif
+                    // Ugly to have that static global LOOLWSD::prisonerServerSocketFD, Otoh we know
+                    // there is just one LOOLWSD object. (Even in real Online.)
+                    lokit_main(LOOLWSD::prisonerServerSocketFD, mobileAppDocId);
                 }).detach();
 #endif
 
@@ -1853,7 +1860,8 @@ static std::shared_ptr<DocumentBroker>
                           const std::string& uri,
                           const std::string& docKey,
                           const std::string& id,
-                          const Poco::URI& uriPublic)
+                          const Poco::URI& uriPublic,
+                          unsigned mobileAppDocId = 0)
 {
     LOG_INF("Find or create DocBroker for docKey [" << docKey <<
             "] for session [" << id << "] on url [" << LOOLWSD::anonymizeUrl(uriPublic.toString()) << "].");
@@ -1925,7 +1933,7 @@ static std::shared_ptr<DocumentBroker>
 
         // Set the one we just created.
         LOG_DBG("New DocumentBroker for docKey [" << docKey << "].");
-        docBroker = std::make_shared<DocumentBroker>(type, uri, uriPublic, docKey);
+        docBroker = std::make_shared<DocumentBroker>(type, uri, uriPublic, docKey, mobileAppDocId);
         DocBrokers.emplace(docKey, docBroker);
         LOG_TRC("Have " << DocBrokers.size() << " DocBrokers after inserting [" << docKey << "].");
     }
@@ -2453,12 +2461,26 @@ private:
         socket->eraseFirstInputBytes(map);
 #else
         Poco::Net::HTTPRequest request;
-        // The 2nd parameter is the response to the HULLO message (which we
-        // respond with the path of the document)
+
+#ifdef IOS
+        // The URL of the document is sent over the FakeSocket by the code in
+        // -[DocumentViewController userContentController:didReceiveScriptMessage:] when it gets the
+        // HULLO message from the JavaScript in global.js.
+
+        // The "app document id", the numeric id of the document, from the appDocIdCounter in CODocument.mm.
+        char *space = strchr(socket->getInBuffer().data(), ' ');
+        assert(space != nullptr);
+        unsigned appDocId = std::strtoul(space + 1, nullptr, 10);
+
+        handleClientWsUpgrade(
+            request, std::string(socket->getInBuffer().data(), space - socket->getInBuffer().data()),
+            disposition, socket, appDocId);
+#else
         handleClientWsUpgrade(
             request, RequestDetails(std::string(socket->getInBuffer().data(),
                                                 socket->getInBuffer().size())),
             disposition, socket);
+#endif
         socket->getInBuffer().clear();
 #endif
     }
@@ -2946,7 +2968,6 @@ private:
 
         throw BadRequestException("Invalid or unknown request.");
     }
-#endif
 
     void handleClientProxyRequest(const Poco::Net::HTTPRequest& request,
                                   const RequestDetails &requestDetails,
@@ -3053,11 +3074,13 @@ private:
             streamSocket->shutdown();
         }
     }
+#endif
 
     void handleClientWsUpgrade(const Poco::Net::HTTPRequest& request,
                                const RequestDetails &requestDetails,
                                SocketDisposition& disposition,
-                               const std::shared_ptr<StreamSocket>& socket)
+                               const std::shared_ptr<StreamSocket>& socket,
+                               unsigned mobileAppDocId = 0)
     {
         const std::string url = requestDetails.getDocumentURI();
         assert(socket && "Must have a valid socket");
@@ -3114,7 +3137,7 @@ private:
             // Request a kit process for this doc.
             std::shared_ptr<DocumentBroker> docBroker = findOrCreateDocBroker(
                 std::static_pointer_cast<ProtocolHandlerInterface>(ws),
-                DocumentBroker::ChildType::Interactive, url, docKey, _id, uriPublic);
+                DocumentBroker::ChildType::Interactive, url, docKey, _id, uriPublic, mobileAppDocId);
             if (docBroker)
             {
                 std::shared_ptr<ClientSession> clientSession =
@@ -3510,11 +3533,13 @@ private:
 
         void wakeupHook() override
         {
+#if !MOBILEAPP
             if (SigUtil::getDumpGlobalState())
             {
                 dump_state();
                 SigUtil::resetDumpGlobalState();
             }
+#endif
         }
     };
     /// This thread & poll accepts incoming connections.
@@ -3971,7 +3996,7 @@ void LOOLWSD::cleanup()
 
 int LOOLWSD::main(const std::vector<std::string>& /*args*/)
 {
-#if MOBILEAPP
+#if MOBILEAPP && !defined IOS
     SigUtil::resetTerminationFlag();
 #endif
 
@@ -3992,10 +4017,6 @@ int LOOLWSD::main(const std::vector<std::string>& /*args*/)
 
     UnitWSD::get().returnValue(returnValue);
 
-#if MOBILEAPP
-    fakeSocketDumpState();
-#endif
-
     LOG_INF("Process [loolwsd] finished.");
     return returnValue;
 }
diff --git a/wsd/LOOLWSD.hpp b/wsd/LOOLWSD.hpp
index 8d9653cd5..54d63962f 100644
--- a/wsd/LOOLWSD.hpp
+++ b/wsd/LOOLWSD.hpp
@@ -34,7 +34,7 @@ class TraceFileWriter;
 class DocumentBroker;
 class ClipboardCache;
 
-std::shared_ptr<ChildProcess> getNewChild_Blocks(const std::string& uri);
+std::shared_ptr<ChildProcess> getNewChild_Blocks(unsigned mobileAppDocId = 0);
 
 // A WSProcess object in the WSD process represents a descendant process, either the direct child
 // process FORKIT or a grandchild KIT process, with which the WSD process communicates through a
@@ -257,8 +257,10 @@ public:
     static std::set<const Poco::Util::AbstractConfiguration*> PluginConfigurations;
     static std::chrono::time_point<std::chrono::system_clock> StartTime;
 #if MOBILEAPP
+#ifndef IOS
     /// This is used to be able to wait until the lokit main thread has finished (and it is safe to load a new document).
     static std::mutex lokit_main_mutex;
+#endif
 #endif
 
     /// For testing only [!]


More information about the Libreoffice-commits mailing list