[Libreoffice-commits] online.git: Branch 'feature/proxyhack' - 15 commits - android/lib cypress_test/integration_tests cypress_test/Makefile.am loleaflet/bundle.js.m4 loleaflet/html loleaflet/js loleaflet/Makefile.am loleaflet/src Makefile.am wsd/DocumentBroker.cpp wsd/DocumentBroker.hpp wsd/FileServer.cpp wsd/LOOLWSD.cpp

Michael Meeks (via logerrit) logerrit at kemper.freedesktop.org
Thu Mar 5 18:41:21 UTC 2020


Rebased ref, commits from common ancestor:
commit 5c1795dc007e9ed8ab341d355efec6bfc6790286
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Wed Mar 4 13:54:04 2020 +0000
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Mar 5 18:40:43 2020 +0000

    Proxy websocket prototype.
    
    Try to read/write avoiding a websocket.
    
    Change-Id: I382039fa88f1030a63df1e47f687df2ee5a6055b

diff --git a/loleaflet/js/global.js b/loleaflet/js/global.js
index f98450fb0..d07854bdf 100644
--- a/loleaflet/js/global.js
+++ b/loleaflet/js/global.js
@@ -166,16 +166,97 @@
 		};
 		this.onopen = function() {
 		};
+		this.close = function() {
+		};
 	};
-
-	global.FakeWebSocket.prototype.close = function() {
-	};
-
 	global.FakeWebSocket.prototype.send = function(data) {
 		this.sendCounter++;
 		window.postMobileMessage(data);
 	};
 
+	global.proxySocketCounter = 0;
+	global.ProxySocket = function (uri) {
+		this.uri = uri;
+		this.binaryType = 'arraybuffer';
+		this.bufferedAmount = 0;
+		this.extensions = '';
+		this.protocol = '';
+		this.connected = true;
+		this.readyState = 0; // connecting
+		this.sessionId = 'fetchsession';
+		this.id = window.proxySocketCounter++;
+		this.sendCounter = 0;
+		this.readWaiting = false;
+		this.onclose = function() {
+		};
+		this.onerror = function() {
+		};
+		this.onmessage = function() {
+		};
+		this.send = function(msg) {
+			console.debug('send msg "' + msg + '"');
+			var req = new XMLHttpRequest();
+			req.open('POST', this.getEndPoint('write'));
+			req.setRequestHeader('SessionId', this.sessionId);
+			if (this.sessionId === 'fetchsession')
+				req.addEventListener('load', function() {
+					console.debug('got session: ' + this.responseText);
+					that.sessionId = this.responseText;
+					that.readyState = 1;
+					that.onopen();
+				});
+			req.send(msg);
+		},
+		this.close = function() {
+			console.debug('close socket');
+			this.readyState = 3;
+			this.onclose();
+		};
+		this.getEndPoint = function(type) {
+			var base = this.uri;
+			return base.replace(/^ws/, 'http') + '/' + type;
+		};
+		console.debug('New proxy socket ' + this.id + ' ' + this.uri);
+
+		// FIXME: perhaps a little risky.
+		this.send('fetchsession');
+		var that = this;
+
+		// horrors ...
+		this.readInterval = setInterval(function() {
+			if (this.readWaiting) // one at a time for now
+				return;
+			if (this.sessionId == 'fetchsession')
+				return; // waiting for our session id.
+			var req = new XMLHttpRequest();
+			// fetch session id:
+			req.addEventListener('load', function() {
+				console.debug('read: ' + this.responseText);
+				if (this.status == 200)
+				{
+					that.onmessage({ data: this.response });
+				}
+				else
+				{
+					console.debug('Handle error ' + this.status);
+				}
+				that.readWaiting = false;
+			});
+			req.open('GET', that.getEndPoint('read'));
+			req.setRequestHeader('SessionId', this.sessionId);
+			req.send(that.sessionId);
+			that.readWaiting = true;
+		}, 250);
+	};
+
+	global.createWebSocket = function(uri) {
+		if (global.socketProxy) {
+			return new global.ProxySocket(uri);
+		} else {
+			return new WebSocket(uri);
+		}
+	};
+
 	// If not debug, don't print anything on the console
 	// except in tile debug mode (Ctrl-Shift-Alt-d)
 	console.log2 = console.log;
@@ -275,7 +356,7 @@
 		var websocketURI = global.host + global.serviceRoot + '/lool/' + encodeURIComponent(global.docURL + (docParams ? '?' + docParams : '')) + '/ws' + wopiSrc;
 
 		try {
-			global.socket = new WebSocket(websocketURI);
+			global.socket = global.createWebSocket(websocketURI);
 		} catch (err) {
 			console.log(err);
 		}
diff --git a/loleaflet/src/core/Socket.js b/loleaflet/src/core/Socket.js
index a173ef784..0d74b9bd8 100644
--- a/loleaflet/src/core/Socket.js
+++ b/loleaflet/src/core/Socket.js
@@ -49,7 +49,7 @@ L.Socket = L.Class.extend({
 			}
 
 			try {
-				this.socket = new WebSocket(this.getWebSocketBaseURI(map) + wopiSrc);
+				this.socket = window.createWebSocket(this.getWebSocketBaseURI(map) + wopiSrc);
 			} catch (e) {
 				// On IE 11 there is a limitation on the number of WebSockets open to a single domain (6 by default and can go to 128).
 				// Detect this and hint the user.
diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp
index eb35d601c..6997e661a 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -2213,6 +2213,13 @@ private:
 //                    Util::dumpHex(std::cerr, "clipboard:\n", "", socket->getInBuffer()); // lots of data ...
                     handleClipboardRequest(request, message, disposition);
                 }
+                else if (request.has("ProxyPrefix") && reqPathTokens.count() > 2 &&
+                         (reqPathTokens[reqPathTokens.count()-2] == "ws"))
+                {
+                    std::string decodedUri; // WOPISrc
+                    Poco::URI::decode(reqPathTokens[1], decodedUri);
+                    handleClientProxyRequest(request, decodedUri, message, disposition);
+                }
                 else if (!(request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0) &&
                     reqPathTokens.count() > 0 && reqPathTokens[0] == "lool")
                 {
@@ -2746,6 +2753,135 @@ private:
     }
 #endif
 
+    void handleClientProxyRequest(const Poco::Net::HTTPRequest& request,
+                                  std::string url,
+                                  Poco::MemoryInputStream& message,
+                                  SocketDisposition &disposition)
+    {
+        if (!request.has("SessionId"))
+            throw BadRequestException("No session id header on proxied request");
+
+        std::string sessionId = request.get("SessionId");
+
+        LOG_INF("URL [" << url << "].");
+        const auto uriPublic = DocumentBroker::sanitizeURI(url);
+        LOG_INF("URI [" << uriPublic.getPath() << "].");
+        const auto docKey = DocumentBroker::getDocKey(uriPublic);
+        LOG_INF("DocKey [" << docKey << "].");
+        const std::string fileId = Util::getFilenameFromURL(docKey);
+        Util::mapAnonymized(fileId, fileId); // Identity mapping, since fileId is already obfuscated
+
+        LOG_INF("Starting Proxy request handler for session [" << _id << "] on url [" << LOOLWSD::anonymizeUrl(url) << "].");
+
+        // Check if readonly session is required
+        bool isReadOnly = false;
+        for (const auto& param : uriPublic.getQueryParameters())
+        {
+            LOG_DBG("Query param: " << param.first << ", value: " << param.second);
+            if (param.first == "permission" && param.second == "readonly")
+            {
+                isReadOnly = true;
+            }
+        }
+
+        LOG_INF("URL [" << LOOLWSD::anonymizeUrl(url) << "] is " << (isReadOnly ? "readonly" : "writable") << ".");
+        (void)request; (void)message; (void)disposition;
+
+#if 0
+        // Request a kit process for this doc.
+        std::shared_ptr<DocumentBroker> docBroker = findOrCreateDocBroker(ws, url, docKey, _id, uriPublic);
+        if (docBroker)
+        {
+            // We can send this back to whomever sent it to us though.
+            const std::string hostNoTrust = (LOOLWSD::ServerName.empty() ? request.getHost() : LOOLWSD::ServerName);
+            // FIXME: Create our own 'ws' -> a crazy one (?)
+            if (sessionId == "fetchsession") {
+                std::shared_ptr<ClientSession> clientSession =
+                    docBroker->createNewClientSession(&ws, _id, uriPublic, isReadOnly, hostNoTrust);
+            }
+
+            // Transfer the client socket to the DocumentBroker when we get back to the poll:
+            disposition.setMove([docBroker, clientSession]
+                                (const std::shared_ptr<Socket> &moveSocket)
+            {
+                // Make sure the thread is running before adding callback.
+                docBroker->startThread();
+
+                // We no longer own this socket.
+                moveSocket->setThreadOwner(std::thread::id());
+
+                docBroker->addCallback([docBroker, moveSocket, clientSession]()
+                {
+                    try
+                    {
+                        // FIXME: lookup the session etc. [!]
+                        std::shared_ptr<ClientSession> clientSession = createNewClientSession(&ws, _id, uriPublic,
+                                                                                              docBroker, isReadOnly, hostNoTrust);
+                        if (clientSession)
+                        {
+
+
+                        auto streamSocket = std::static_pointer_cast<StreamSocket>(moveSocket);
+
+                        // Set the ClientSession to handle Socket events.
+                        streamSocket->setHandler(clientSession);
+                        LOG_DBG("Socket #" << moveSocket->getFD() << " handler is " << clientSession->getName());
+
+                        // Move the socket into DocBroker.
+                        docBroker->addSocketToPoll(moveSocket);
+
+                        // Add and load the session.
+                        docBroker->addSession(clientSession);
+
+                        checkDiskSpaceAndWarnClients(true);
+#if !ENABLE_SUPPORT_KEY
+                        // Users of development versions get just an info when reaching max documents or connections
+                        checkSessionLimitsAndWarnClients();
+#endif
+                        }
+                        catch (const UnauthorizedRequestException& exc)
+                        {
+                            LOG_ERR("Unauthorized Request while loading session for " << docBroker->getDocKey() << ": " << exc.what());
+                            const std::string msg = "error: cmd=internal kind=unauthorized";
+                            clientSession->sendMessage(msg);
+                        }
+                        catch (const StorageConnectionException& exc)
+                        {
+                            // Alert user about failed load
+                            const std::string msg = "error: cmd=storage kind=loadfailed";
+                            clientSession->sendMessage(msg);
+                        }
+                        catch (const std::exception& exc)
+                        {
+                            LOG_ERR("Error while loading : " << exc.what());
+
+                            // Alert user about failed load
+                            const std::string msg = "error: cmd=storage kind=loadfailed";
+                            clientSession->sendMessage(msg);
+                        }
+                    });
+                });
+                }
+                else
+                {
+                    LOG_WRN("Failed to create Client Session with id [" << _id << "] on docKey [" << docKey << "].");
+                }
+            }
+            else
+            {
+                throw ServiceUnavailableException("Failed to create DocBroker with docKey [" + docKey + "].");
+            }
+        }
+        catch (const std::exception& exc)
+        {
+            LOG_ERR("Error while handling Client WS Request: " << exc.what());
+            const std::string msg = "error: cmd=internal kind=load";
+            ws.sendMessage(msg);
+            ws.shutdown(WebSocketHandler::StatusCodes::ENDPOINT_GOING_AWAY, msg);
+        }
+#endif
+    }
+
     void handleClientWsUpgrade(const Poco::Net::HTTPRequest& request, const std::string& url,
                                SocketDisposition &disposition)
     {
commit 6ac977439e903d1f66b04b9c12b3ecc89fdea1bb
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Wed Mar 4 13:52:51 2020 +0000
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Mar 5 17:06:46 2020 +0000

    ProxyPrefix: allow the user to specify a custom prefix.
    
    This allows us to re-direct web traffic via a proxy quite simply
    during fetch, instead of changing the service root.
    
    Change-Id: I28d348467e48394d581fca4da4c199348a2ca8e0

diff --git a/loleaflet/html/loleaflet.html.m4 b/loleaflet/html/loleaflet.html.m4
index 84fd50b15..86e54249f 100644
--- a/loleaflet/html/loleaflet.html.m4
+++ b/loleaflet/html/loleaflet.html.m4
@@ -235,6 +235,7 @@ m4_ifelse(MOBILEAPP,[true],
       window.reuseCookies = '';
       window.protocolDebug = false;
       window.frameAncestors = '';
+      window.socketProxy = false;
       window.tileSize = 256;],
      [window.host = '%HOST%';
       window.serviceRoot = '%SERVICE_ROOT%';
@@ -247,6 +248,7 @@ m4_ifelse(MOBILEAPP,[true],
       window.reuseCookies = '%REUSE_COOKIES%';
       window.protocolDebug = %PROTOCOL_DEBUG%;
       window.frameAncestors = '%FRAME_ANCESTORS%';
+      window.socketProxy = %SOCKET_PROXY%;
       window.tileSize = 256;])
 m4_syscmd([cat ]GLOBAL_JS)m4_dnl
     </script>
diff --git a/wsd/FileServer.cpp b/wsd/FileServer.cpp
index d7db1e80c..cc4cce509 100644
--- a/wsd/FileServer.cpp
+++ b/wsd/FileServer.cpp
@@ -594,6 +594,17 @@ constexpr char BRANDING[] = "branding";
 constexpr char BRANDING_UNSUPPORTED[] = "branding-unsupported";
 #endif
 
+namespace {
+    // The user can override the ServerRoot with a new prefix.
+    std::string getResponseRoot(const HTTPRequest &request)
+    {
+        if (!request.has("ProxyPrefix"))
+            return LOOLWSD::ServiceRoot;
+        std::string proxyPrefix = request.get("ProxyPrefix", "");
+        return proxyPrefix;
+    }
+}
+
 void FileServerRequestHandler::preprocessFile(const HTTPRequest& request, Poco::MemoryInputStream& message,
                                               const std::shared_ptr<StreamSocket>& socket)
 {
@@ -640,15 +651,21 @@ void FileServerRequestHandler::preprocessFile(const HTTPRequest& request, Poco::
         }
     }
 
-    const auto& config = Application::instance().config();
+    std::string socketProxy = "false";
+    if (request.has("ProxyPrefix"))
+        socketProxy = "true";
+    Poco::replaceInPlace(preprocess, std::string("%SOCKET_PROXY%"), socketProxy);
+
+    std::string responseRoot = getResponseRoot(request);
 
     Poco::replaceInPlace(preprocess, std::string("%ACCESS_TOKEN%"), escapedAccessToken);
     Poco::replaceInPlace(preprocess, std::string("%ACCESS_TOKEN_TTL%"), std::to_string(tokenTtl));
     Poco::replaceInPlace(preprocess, std::string("%ACCESS_HEADER%"), escapedAccessHeader);
     Poco::replaceInPlace(preprocess, std::string("%HOST%"), host);
     Poco::replaceInPlace(preprocess, std::string("%VERSION%"), std::string(LOOLWSD_VERSION_HASH));
-    Poco::replaceInPlace(preprocess, std::string("%SERVICE_ROOT%"), LOOLWSD::ServiceRoot);
+    Poco::replaceInPlace(preprocess, std::string("%SERVICE_ROOT%"), responseRoot);
 
+    const auto& config = Application::instance().config();
     std::string protocolDebug = "false";
     if (config.getBool("logging.protocol"))
         protocolDebug = "true";
@@ -657,16 +674,16 @@ void FileServerRequestHandler::preprocessFile(const HTTPRequest& request, Poco::
     static const std::string linkCSS("<link rel=\"stylesheet\" href=\"%s/loleaflet/" LOOLWSD_VERSION_HASH "/%s.css\">");
     static const std::string scriptJS("<script src=\"%s/loleaflet/" LOOLWSD_VERSION_HASH "/%s.js\"></script>");
 
-    std::string brandCSS(Poco::format(linkCSS, LOOLWSD::ServiceRoot, std::string(BRANDING)));
-    std::string brandJS(Poco::format(scriptJS, LOOLWSD::ServiceRoot, std::string(BRANDING)));
+    std::string brandCSS(Poco::format(linkCSS, responseRoot, std::string(BRANDING)));
+    std::string brandJS(Poco::format(scriptJS, responseRoot, std::string(BRANDING)));
 
 #if ENABLE_SUPPORT_KEY
     const std::string keyString = config.getString("support_key", "");
     SupportKey key(keyString);
     if (!key.verify() || key.validDaysRemaining() <= 0)
     {
-        brandCSS = Poco::format(linkCSS, LOOLWSD::ServiceRoot, std::string(BRANDING_UNSUPPORTED));
-        brandJS = Poco::format(scriptJS, LOOLWSD::ServiceRoot, std::string(BRANDING_UNSUPPORTED));
+        brandCSS = Poco::format(linkCSS, responseRoot, std::string(BRANDING_UNSUPPORTED));
+        brandJS = Poco::format(scriptJS, responseRoot, std::string(BRANDING_UNSUPPORTED));
     }
 #endif
 
@@ -849,13 +866,15 @@ void FileServerRequestHandler::preprocessAdminFile(const HTTPRequest& request,co
     if (!FileServerRequestHandler::isAdminLoggedIn(request, response))
         throw Poco::Net::NotAuthenticatedException("Invalid admin login");
 
+    std::string responseRoot = getResponseRoot(request);
+
     static const std::string scriptJS("<script src=\"%s/loleaflet/" LOOLWSD_VERSION_HASH "/%s.js\"></script>");
     static const std::string footerPage("<div class=\"footer navbar-fixed-bottom text-info text-center\"><strong>Key:</strong> %s   <strong>Expiry Date:</strong> %s</div>");
 
     const std::string relPath = getRequestPathname(request);
     LOG_DBG("Preprocessing file: " << relPath);
     std::string adminFile = *getUncompressedFile(relPath);
-    std::string brandJS(Poco::format(scriptJS, LOOLWSD::ServiceRoot, std::string(BRANDING)));
+    std::string brandJS(Poco::format(scriptJS, responseRoot, std::string(BRANDING)));
     std::string brandFooter;
 
 #if ENABLE_SUPPORT_KEY
@@ -873,7 +892,7 @@ void FileServerRequestHandler::preprocessAdminFile(const HTTPRequest& request,co
     Poco::replaceInPlace(adminFile, std::string("<!--%BRANDING_JS%-->"), brandJS);
     Poco::replaceInPlace(adminFile, std::string("<!--%FOOTER%-->"), brandFooter);
     Poco::replaceInPlace(adminFile, std::string("%VERSION%"), std::string(LOOLWSD_VERSION_HASH));
-    Poco::replaceInPlace(adminFile, std::string("%SERVICE_ROOT%"), LOOLWSD::ServiceRoot);
+    Poco::replaceInPlace(adminFile, std::string("%SERVICE_ROOT%"), responseRoot);
 
     // Ask UAs to block if they detect any XSS attempt
     response.add("X-XSS-Protection", "1; mode=block");
commit da944760a62a82c21a32017b9c04526ea00d3cbd
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Wed Mar 4 21:56:48 2020 +0000
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Mar 5 17:10:36 2020 +0100

    re-factor: move createNewSession into DocumentBroker.
    
    Change-Id: I78f07a61fd79dfdd1c0d0ef21cf19218beec46ee
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90025
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Michael Meeks <michael.meeks at collabora.com>

diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp
index 74b827678..18bcdce63 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -1497,6 +1497,38 @@ void DocumentBroker::finalRemoveSession(const std::string& id)
     }
 }
 
+std::shared_ptr<ClientSession> DocumentBroker::createNewClientSession(const WebSocketHandler* ws,
+                                                                      const std::string& id,
+                                                                      const Poco::URI& uriPublic,
+                                                                      const bool isReadOnly,
+                                                                      const std::string& hostNoTrust)
+{
+    try
+    {
+        // Now we have a DocumentBroker and we're ready to process client commands.
+        if (ws)
+        {
+            const std::string statusReady = "statusindicator: ready";
+            LOG_TRC("Sending to Client [" << statusReady << "].");
+            ws->sendMessage(statusReady);
+        }
+
+        // In case of WOPI, if this session is not set as readonly, it might be set so
+        // later after making a call to WOPI host which tells us the permission on files
+        // (UserCanWrite param).
+        auto session = std::make_shared<ClientSession>(id, shared_from_this(), uriPublic, isReadOnly, hostNoTrust);
+        session->construct();
+
+        return session;
+    }
+    catch (const std::exception& exc)
+    {
+        LOG_WRN("Exception while preparing session [" << id << "]: " << exc.what());
+    }
+
+    return nullptr;
+}
+
 void DocumentBroker::addCallback(const SocketPoll::CallbackFn& fn)
 {
     _poll->addCallback(fn);
diff --git a/wsd/DocumentBroker.hpp b/wsd/DocumentBroker.hpp
index 5e3bea9aa..f56bd1e3f 100644
--- a/wsd/DocumentBroker.hpp
+++ b/wsd/DocumentBroker.hpp
@@ -243,6 +243,13 @@ public:
     /// Hard removes a session by ID, only for ClientSession.
     void finalRemoveSession(const std::string& id);
 
+    /// Create new client session
+    std::shared_ptr<ClientSession> createNewClientSession(const WebSocketHandler* ws,
+                                                          const std::string& id,
+                                                          const Poco::URI& uriPublic,
+                                                          const bool isReadOnly,
+                                                          const std::string& hostNoTrust);
+
     /// Thread safe termination of this broker if it has a lingering thread
     void joinThread();
 
diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp
index 61b9d8f09..eb35d601c 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -1780,40 +1780,6 @@ static std::shared_ptr<DocumentBroker> findOrCreateDocBroker(WebSocketHandler& w
     return docBroker;
 }
 
-static std::shared_ptr<ClientSession> createNewClientSession(const WebSocketHandler* ws,
-                                                             const std::string& id,
-                                                             const Poco::URI& uriPublic,
-                                                             const std::shared_ptr<DocumentBroker>& docBroker,
-                                                             const bool isReadOnly,
-                                                             const std::string& hostNoTrust)
-{
-    LOG_CHECK_RET(docBroker && "Null docBroker instance", nullptr);
-    try
-    {
-        // Now we have a DocumentBroker and we're ready to process client commands.
-        if (ws)
-        {
-            const std::string statusReady = "statusindicator: ready";
-            LOG_TRC("Sending to Client [" << statusReady << "].");
-            ws->sendMessage(statusReady);
-        }
-
-        // In case of WOPI, if this session is not set as readonly, it might be set so
-        // later after making a call to WOPI host which tells us the permission on files
-        // (UserCanWrite param).
-        auto session = std::make_shared<ClientSession>(id, docBroker, uriPublic, isReadOnly, hostNoTrust);
-        session->construct();
-
-        return session;
-    }
-    catch (const std::exception& exc)
-    {
-        LOG_WRN("Exception while preparing session [" << id << "]: " << exc.what());
-    }
-
-    return nullptr;
-}
-
 /// Handles the socket that the prisoner kit connected to WSD on.
 class PrisonerRequestDispatcher : public WebSocketHandler
 {
@@ -2850,8 +2816,8 @@ private:
                 const std::string hostNoTrust = (LOOLWSD::ServerName.empty() ? request.getHost() : LOOLWSD::ServerName);
 #endif
 
-                std::shared_ptr<ClientSession> clientSession = createNewClientSession(&ws, _id, uriPublic,
-                                                                                      docBroker, isReadOnly, hostNoTrust);
+                std::shared_ptr<ClientSession> clientSession =
+                    docBroker->createNewClientSession(&ws, _id, uriPublic, isReadOnly, hostNoTrust);
                 if (clientSession)
                 {
                     // Transfer the client socket to the DocumentBroker when we get back to the poll:
commit 8a8eb43375d824658013291c8cd8c12a57416b2e
Author:     Henry Castro <hcastro at collabora.com>
AuthorDate: Wed Mar 4 09:09:47 2020 -0400
Commit:     Henry Castro <hcastro at collabora.com>
CommitDate: Thu Mar 5 13:07:25 2020 +0100

    wsd: makefile: run server with disabled capabilities
    
    fix runing the server with --disable-setcap, very useful
    to debug when attaching kit process
    
    Change-Id: I5ded5a1aa1924a9325ae76be46b32c0020d8ce35
    WARNING: This is just for development and debugging purposes
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/89970
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Henry Castro <hcastro at collabora.com>

diff --git a/Makefile.am b/Makefile.am
index bf5725b92..f183ad6ff 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -302,6 +302,7 @@ EXTRA_DIST = discovery.xml \
 if HAVE_LO_PATH
 
 SYSTEM_STAMP = @SYSTEMPLATE_PATH@/system_stamp
+CAPABILITIES = $(if @ENABLE_SETCAP@,true,false)
 
 $(SYSTEM_STAMP) : ${top_srcdir}/loolwsd-systemplate-setup
 	if test "z at SYSTEMPLATE_PATH@" != "z"; then rm -rf "@SYSTEMPLATE_PATH@"; fi
@@ -322,6 +323,7 @@ run: all @JAILS_PATH@
 	@cp $(abs_top_srcdir)/test/data/hello.odp $(abs_top_srcdir)/test/data/hello-world.odp
 	@echo
 	./loolwsd --o:sys_template_path="@SYSTEMPLATE_PATH@" \
+			  --o:security.capabilities="$(CAPABILITIES)" \
 			  --o:child_root_path="@JAILS_PATH@" --o:storage.filesystem[@allow]=true \
 			  --o:ssl.cert_file_path="$(abs_top_srcdir)/etc/cert.pem" \
 			  --o:ssl.key_file_path="$(abs_top_srcdir)/etc/key.pem" \
commit 6114b8c8cef37ec517a7b5c8fb47fd144d7ad03a
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Wed Mar 4 20:18:30 2020 +0100
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Mar 4 21:10:13 2020 +0100

    cypress: mobile: enable some mobile wizard's state related checks.
    
    Change-Id: I0bf19450f14e8e8d99e1a103558499d53f21f760
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/89995
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Tamás Zolnai <tamas.zolnai at collabora.com>

diff --git a/cypress_test/integration_tests/mobile/writer/mobile_wizard_state_spec.js b/cypress_test/integration_tests/mobile/writer/mobile_wizard_state_spec.js
index 676eb2703..47790dc47 100644
--- a/cypress_test/integration_tests/mobile/writer/mobile_wizard_state_spec.js
+++ b/cypress_test/integration_tests/mobile/writer/mobile_wizard_state_spec.js
@@ -45,9 +45,8 @@ describe('Mobile wizard state tests', function() {
 			.click();
 
 		// Mobile wizard is opened and it has any content
-		// TODO: fix this bug
-		/*cy.get('#mobile-wizard-content')
-			.should('not.be.empty'); */
+		cy.get('#mobile-wizard-content')
+			.should('not.be.empty');
 		cy.get('#tb_actionbar_item_mobile_wizard table')
 			.should('have.class', 'checked');
 	});
@@ -82,9 +81,8 @@ describe('Mobile wizard state tests', function() {
 		cy.get('#tb_actionbar_item_mobile_wizard')
 			.click();
 
-		// TODO: fix this bug
-		//cy.get('#mobile-wizard-content')
-		//	.should('not.be.empty');
+		cy.get('#mobile-wizard-content')
+			.should('not.be.empty');
 		cy.get('#tb_actionbar_item_mobile_wizard table')
 			.should('have.class', 'checked');
 	});
commit 883b28fbd5d170bec7213f60db2aac55658827a7
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Wed Mar 4 13:11:28 2020 +0100
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Wed Mar 4 13:58:33 2020 +0100

    cypress: mobile: re-enable some skipped tests.
    
    Change-Id: I4163fdba2bc0fb7bcdc4d2b33f5deff6b0857013
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/89960
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Tamás Zolnai <tamas.zolnai at collabora.com>

diff --git a/cypress_test/integration_tests/mobile/writer/focus_spec.js b/cypress_test/integration_tests/mobile/writer/focus_spec.js
index dce4cd646..919b25d72 100644
--- a/cypress_test/integration_tests/mobile/writer/focus_spec.js
+++ b/cypress_test/integration_tests/mobile/writer/focus_spec.js
@@ -151,7 +151,7 @@ describe('Focus tests', function() {
 			.should('be.eq', 'clipboard');
 	});
 
-	it.skip('Shape related focus.', function() {
+	it('Shape related focus.', function() {
 		// Click on edit button
 		cy.get('#mobile-edit-button').click();
 
diff --git a/cypress_test/integration_tests/mobile/writer/shape_properties_spec.js b/cypress_test/integration_tests/mobile/writer/shape_properties_spec.js
index edca39a77..eb01e78e5 100644
--- a/cypress_test/integration_tests/mobile/writer/shape_properties_spec.js
+++ b/cypress_test/integration_tests/mobile/writer/shape_properties_spec.js
@@ -220,7 +220,7 @@ describe('Change shape properties via mobile wizard.', function() {
 			.should('have.attr', 'stroke', 'rgb(152,0,0)');
 	});
 
-	it.skip('Change line style', function() {
+	it('Change line style', function() {
 		// TODO: Layout of the line properties panel is completely broken.
 		if (Cypress.env('LO_CORE_VERSION') === 'master')
 			return;
commit c58b8dfdfc395ffadab780aa76f7b2a98c92b051
Author:     Henry Castro <hcastro at collabora.com>
AuthorDate: Tue Mar 3 10:44:19 2020 -0400
Commit:     Henry Castro <hcastro at collabora.com>
CommitDate: Wed Mar 4 13:30:41 2020 +0100

    loleaflet: makefile: prefix m4_ builtin functions
    
    Change-Id: I3605da8b44cec9855be39846de7f10feaf075f7d
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/89890
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Tested-by: Henry Castro <hcastro at collabora.com>
    Reviewed-by: Henry Castro <hcastro at collabora.com>

diff --git a/loleaflet/Makefile.am b/loleaflet/Makefile.am
index 968cfabbc..915117a83 100644
--- a/loleaflet/Makefile.am
+++ b/loleaflet/Makefile.am
@@ -392,7 +392,7 @@ $(builddir)/dist/loleaflet.html: $(srcdir)/html/loleaflet.html.m4 $(LOLEAFLET_HT
 \
 	$(builddir)/dist/bundle.css $(builddir)/dist/bundle.js
 	@echo "Generating loleaflet.html..."
-	@m4 -E -DDEBUG=$(ENABLE_DEBUG) \
+	@m4 -PE -DDEBUG=$(ENABLE_DEBUG) \
 		-DIOSAPP=$(ENABLE_IOSAPP) \
 		-DGTKAPP=$(ENABLE_GTKAPP) \
 		-DANDROIDAPP=$(ENABLE_ANDROIDAPP) \
diff --git a/loleaflet/html/loleaflet.html.m4 b/loleaflet/html/loleaflet.html.m4
index 9cba7427a..84fd50b15 100644
--- a/loleaflet/html/loleaflet.html.m4
+++ b/loleaflet/html/loleaflet.html.m4
@@ -1,10 +1,10 @@
-dnl -*- Mode: HTML -*-x
-changequote([,])dnl
-dnl# foreachq(x, `item_1, item_2, ..., item_n', stmt)
-dnl# quoted list, alternate improved version
-define([foreachq],[ifelse([$2],[],[],[pushdef([$1])_$0([$1],[$3],[],$2)popdef([$1])])])dnl
-define([_foreachq],[ifelse([$#],[3],[],[define([$1],[$4])$2[]$0([$1],[$2],shift(shift(shift($@))))])])dnl
-define(_YEAR_,esyscmd(date +%Y|tr -d '\n'))
+m4_dnl -*- Mode: HTML -*-x
+m4_changequote([,])m4_dnl
+m4_dnl# m4_foreachq(x, `item_1, item_2, ..., item_n', stmt)
+m4_dnl# quoted list, alternate improved version
+m4_define([m4_foreachq],[m4_ifelse([$2],[],[],[m4_pushdef([$1])_$0([$1],[$3],[],$2)m4_popdef([$1])])])m4_dnl
+m4_define([_m4_foreachq],[m4_ifelse([$#],[3],[],[m4_define([$1],[$4])$2[]$0([$1],[$2],m4_shift(m4_shift(m4_shift($@))))])])m4_dnl
+m4_define(_YEAR_,m4_esyscmd(date +%Y|tr -d '\n'))
 <!DOCTYPE html>
 <!-- saved from url=(0054)http://leafletjs.com/examples/quick-start-example.html -->
 <html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
@@ -13,13 +13,13 @@ define(_YEAR_,esyscmd(date +%Y|tr -d '\n'))
 <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">
 
 <script>
-dnl# Define MOBILEAPP as true if this is either for the iOS app or for the gtk+ "app" testbed
-define([MOBILEAPP],[])
-ifelse(IOSAPP,[true],[define([MOBILEAPP],[true])])
-ifelse(GTKAPP,[true],[define([MOBILEAPP],[true])])
-ifelse(ANDROIDAPP,[true],[define([MOBILEAPP],[true])])
+m4_dnl# Define MOBILEAPP as true if this is either for the iOS app or for the gtk+ "app" testbed
+m4_define([MOBILEAPP],[])
+m4_ifelse(IOSAPP,[true],[m4_define([MOBILEAPP],[true])])
+m4_ifelse(GTKAPP,[true],[m4_define([MOBILEAPP],[true])])
+m4_ifelse(ANDROIDAPP,[true],[m4_define([MOBILEAPP],[true])])
 
-ifelse(MOBILEAPP,[],
+m4_ifelse(MOBILEAPP,[],
   // Start listening for Host_PostmessageReady message and save the
   // result for future
   window.WOPIpostMessageReady = false;
@@ -33,12 +33,12 @@ ifelse(MOBILEAPP,[],
     }
   };
   window.addEventListener('message', PostMessageReadyListener, false);
-)dnl
+)m4_dnl
 
-dnl# For use in conditionals in JS: window.ThisIsAMobileApp, window.ThisIsTheiOSApp,
-dnl# and window.ThisIsTheGtkApp
+m4_dnl# For use in conditionals in JS: window.ThisIsAMobileApp, window.ThisIsTheiOSApp,
+m4_dnl# and window.ThisIsTheGtkApp
 
-ifelse(MOBILEAPP,[true],
+m4_ifelse(MOBILEAPP,[true],
   [   window.ThisIsAMobileApp = true;
    window.open = function (url, windowName, windowFeatures) {
      window.postMobileMessage('HYPERLINK ' + url); /* don't call the 'normal' window.open on mobile at all */
@@ -46,21 +46,21 @@ ifelse(MOBILEAPP,[true],
    window.MobileAppName='MOBILEAPPNAME';],
   [   window.ThisIsAMobileApp = false;]
 )
-ifelse(IOSAPP,[true],
+m4_ifelse(IOSAPP,[true],
   [   window.ThisIsTheiOSApp = true;
    window.postMobileMessage = function(msg) { window.webkit.messageHandlers.lool.postMessage(msg, '*'); };
    window.postMobileError   = function(msg) { window.webkit.messageHandlers.error.postMessage(msg, '*'); };
    window.postMobileDebug   = function(msg) { window.webkit.messageHandlers.debug.postMessage(msg, '*'); };],
   [   window.ThisIsTheiOSApp = false;]
 )
-ifelse(GTKAPP,[true],
+m4_ifelse(GTKAPP,[true],
   [   window.ThisIsTheGtkApp = true;
    window.postMobileMessage = function(msg) { window.webkit.messageHandlers.lool.postMessage(msg, '*'); };
    window.postMobileError   = function(msg) { window.webkit.messageHandlers.error.postMessage(msg, '*'); };
    window.postMobileDebug   = function(msg) { window.webkit.messageHandlers.debug.postMessage(msg, '*'); };],
   [   window.ThisIsTheGtkApp = false;]
 )
-ifelse(ANDROIDAPP,[true],
+m4_ifelse(ANDROIDAPP,[true],
   [   window.ThisIsTheAndroidApp = true;
    window.postMobileMessage = function(msg) { window.LOOLMessageHandler.postMobileMessage(msg); };
    window.postMobileError   = function(msg) { window.LOOLMessageHandler.postMobileError(msg); };
@@ -90,26 +90,26 @@ var Base64ToArrayBuffer = function(base64Str) {
 
 </script>
 
-ifelse(MOBILEAPP,[true],
-  [ifelse(DEBUG,[true],
-    foreachq([fileCSS],[LOLEAFLET_CSS],[<link rel="stylesheet" href="fileCSS" />
+m4_ifelse(MOBILEAPP,[true],
+  [m4_ifelse(DEBUG,[true],
+    m4_foreachq([fileCSS],[LOLEAFLET_CSS],[<link rel="stylesheet" href="fileCSS" />
   ]),
-    [<style>syscmd([cat ]BUNDLE_CSS)</style>
+    [<style>m4_syscmd([cat ]BUNDLE_CSS)</style>
   ])],
-  [ifelse(DEBUG,[true],
-    foreachq([fileCSS],[LOLEAFLET_CSS],[<link rel="stylesheet" href="%SERVICE_ROOT%/loleaflet/%VERSION%/fileCSS" />
+  [m4_ifelse(DEBUG,[true],
+    m4_foreachq([fileCSS],[LOLEAFLET_CSS],[<link rel="stylesheet" href="%SERVICE_ROOT%/loleaflet/%VERSION%/fileCSS" />
   ]),
-    [<style>syscmd([cat ]BUNDLE_CSS)</style>
-  ])]dnl
-)dnl
+    [<style>m4_syscmd([cat ]BUNDLE_CSS)</style>
+  ])]m4_dnl
+)m4_dnl
 <!--%BRANDING_CSS%--> <!-- add your logo here -->
-ifelse(IOSAPP,[true],
+m4_ifelse(IOSAPP,[true],
   [<link rel="stylesheet" href="Branding/branding.css">])
-ifelse(ANDROIDAPP,[true],
+m4_ifelse(ANDROIDAPP,[true],
   [<link rel="stylesheet" href="branding.css">])
-ifelse(MOBILEAPP,[true],
+m4_ifelse(MOBILEAPP,[true],
   [
-   ifelse(IOSAPP,[true],
+   m4_ifelse(IOSAPP,[true],
      [],
      [<link rel="localizations" href="l10n/uno-localizations-override.json" type="application/vnd.oftn.l10n+json"/>
       <link rel="localizations" href="l10n/localizations.json" type="application/vnd.oftn.l10n+json"/>
@@ -121,7 +121,7 @@ ifelse(MOBILEAPP,[true],
    <link rel="localizations" href="%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/locore-localizations.json" type="application/vnd.oftn.l10n+json"/>
    <link rel="localizations" href="%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/help-localizations.json" type="application/vnd.oftn.l10n+json"/>
    <link rel="localizations" href="%SERVICE_ROOT%/loleaflet/%VERSION%/l10n/uno-localizations.json" type="application/vnd.oftn.l10n+json"/>]
-)dnl
+)m4_dnl
 </head>
 
   <body style="user-select: none;">
@@ -223,7 +223,7 @@ ifelse(MOBILEAPP,[true],
     </div>
 
     <script defer>
-ifelse(MOBILEAPP,[true],
+m4_ifelse(MOBILEAPP,[true],
      [window.host = '';
       window.serviceRoot = '';
       window.accessToken = '';
@@ -248,20 +248,20 @@ ifelse(MOBILEAPP,[true],
       window.protocolDebug = %PROTOCOL_DEBUG%;
       window.frameAncestors = '%FRAME_ANCESTORS%';
       window.tileSize = 256;])
-syscmd([cat ]GLOBAL_JS)dnl
+m4_syscmd([cat ]GLOBAL_JS)m4_dnl
     </script>
 
-ifelse(MOBILEAPP,[true],
-  ifelse(DEBUG,[true],foreachq([fileJS],[LOLEAFLET_JS],
+m4_ifelse(MOBILEAPP,[true],
+  m4_ifelse(DEBUG,[true],m4_foreachq([fileJS],[LOLEAFLET_JS],
   [    <script src="fileJS" defer></script>
   ]),
   [    <script src="bundle.js" defer></script>
   ]),
-  ifelse(DEBUG,[true],foreachq([fileJS],[LOLEAFLET_JS],
+  m4_ifelse(DEBUG,[true],m4_foreachq([fileJS],[LOLEAFLET_JS],
   [    <script src="%SERVICE_ROOT%/loleaflet/%VERSION%/fileJS" defer></script>
   ]),
   [    <script src="%SERVICE_ROOT%/loleaflet/%VERSION%/bundle.js" defer></script>
   ])
-)dnl
+)m4_dnl
     <!--%BRANDING_JS%--> <!-- logo onclick handler -->
 </body></html>
commit 0897397834608ff8f87382cac896f8912260616c
Author:     Tomaž Vajngerl <tomaz.vajngerl at collabora.co.uk>
AuthorDate: Wed Mar 4 11:33:17 2020 +0100
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Wed Mar 4 13:28:07 2020 +0100

    android: create mainHandler on demand to prevent NPE
    
    A crash happens when "back" is hit with a NPE exception because
    mainHandler is null (not yet initilized). This changes the code
    so mainHandler is initialized on demand and if not before, then
    at least when initUI is called.
    
    Change-Id: Id20b2093315ba41dac205fc4e94f31d00a4fe1f1
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/89953
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Michael Meeks <michael.meeks at collabora.com>

diff --git a/android/lib/src/main/java/org/libreoffice/androidlib/LOActivity.java b/android/lib/src/main/java/org/libreoffice/androidlib/LOActivity.java
index c7796d05c..d54ab18ce 100644
--- a/android/lib/src/main/java/org/libreoffice/androidlib/LOActivity.java
+++ b/android/lib/src/main/java/org/libreoffice/androidlib/LOActivity.java
@@ -100,7 +100,7 @@ public class LOActivity extends AppCompatActivity {
     private String urlToLoad;
     private WebView mWebView;
     private SharedPreferences sPrefs;
-    private Handler mainHandler;
+    private Handler mMainHandler = null;
 
     private boolean isDocEditable = false;
     private boolean isDocDebuggable = BuildConfig.DEBUG;
@@ -246,6 +246,13 @@ public class LOActivity extends AppCompatActivity {
         return true;
     }
 
+    private Handler getMainHandler() {
+        if (mMainHandler == null) {
+            mMainHandler = new Handler(getMainLooper());
+        }
+        return mMainHandler;
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -350,7 +357,7 @@ public class LOActivity extends AppCompatActivity {
             }
         }
 
-        mainHandler = new Handler(getMainLooper());
+        getMainHandler();
 
         clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
         nativeMsgThread = new Thread(new Runnable() {
@@ -408,7 +415,7 @@ public class LOActivity extends AppCompatActivity {
 
         final Intent finalIntent = intent;
         mProgressDialog.indeterminate(R.string.saving);
-        mainHandler.post(new Runnable() {
+        getMainHandler().post(new Runnable() {
             @Override
             public void run() {
                 documentLoaded = false;
@@ -642,7 +649,7 @@ public class LOActivity extends AppCompatActivity {
 
         // The 'BYE' takes a considerable amount of time, we need to post it
         // so that it starts after the saving progress is actually shown
-        mainHandler.post(new Runnable() {
+        getMainHandler().post(new Runnable() {
             @Override
             public void run() {
                 documentLoaded = false;
@@ -810,7 +817,7 @@ public class LOActivity extends AppCompatActivity {
                 finishWithProgress();
                 return false;
             case "PRINT":
-                mainHandler.post(new Runnable() {
+                getMainHandler().post(new Runnable() {
                     @Override
                     public void run() {
                         LOActivity.this.initiatePrint();
@@ -836,7 +843,7 @@ public class LOActivity extends AppCompatActivity {
                 }
                 break;
             case "DIM_SCREEN": {
-                mainHandler.post(new Runnable() {
+                getMainHandler().post(new Runnable() {
                     @Override
                     public void run() {
                         getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
@@ -845,7 +852,7 @@ public class LOActivity extends AppCompatActivity {
                 return false;
             }
             case "LIGHT_SCREEN": {
-                mainHandler.post(new Runnable() {
+                getMainHandler().post(new Runnable() {
                     @Override
                     public void run() {
                         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
commit 175cbdb3f436981acac46bb6748545890712cb44
Author:     Henry Castro <hcastro at collabora.com>
AuthorDate: Fri Feb 21 15:59:23 2020 -0400
Commit:     Henry Castro <hcastro at collabora.com>
CommitDate: Tue Mar 3 18:21:10 2020 +0100

    loleaflet: makefile: use m4 to bundle all js files
    
    It is a flexible bundling of all javascript libraries and
    each platform (ANDROID, IOS, Desktop) can remove or add
    from node_modules or imported according to its requirements.
    
    Maybe in a near future some external libraries could be loaded
    on demand or lazy loading that are not necessary when the page
    is loading.
    
    Change-Id: Ief5d4973ab29a4fb3c4848c3e1653a24fff0da69
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/89860
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Tested-by: Henry Castro <hcastro at collabora.com>
    Reviewed-by: Henry Castro <hcastro at collabora.com>

diff --git a/loleaflet/Makefile.am b/loleaflet/Makefile.am
index 2710f180e..968cfabbc 100644
--- a/loleaflet/Makefile.am
+++ b/loleaflet/Makefile.am
@@ -378,11 +378,13 @@ $(builddir)/dist/bundle.js: $(NODE_MODULES_JS_SRC) \
 	$(LOLEAFLET_PREFIX)/dist/loleaflet-src.js \
 	$(LOLEAFLET_LIBS_JS_SRC)
 	@echo "Uglify loleaflet js files..."
+	@m4 -PE -DL10N_IOS_ALL_JS=$(L10N_IOS_ALL_JS) \
+		-DNODE_MODULES_JS=$(subst $(SPACE),$(COMMA),$(NODE_MODULES_JS)) \
+		-DLOLEAFLET_LIBS_JS=$(subst $(SPACE),$(COMMA),$(LOLEAFLET_LIBS_JS_SRC)) \
+		-DLOLEAFLET_JS=$(builddir)/build/dist/loleaflet-src.js \
+		$(srcdir)/bundle.js.m4 > $(LOLEAFLET_PREFIX)/dist/bundle.js
 	NODE_PATH=$(abs_builddir)/node_modules $(NODE) node_modules/uglify-js/bin/uglifyjs \
-		$(L10N_IOS_ALL_JS) \
-		$(NODE_MODULES_JS) \
-		$(LOLEAFLET_LIBS_JS_SRC) \
-		$(builddir)/build/dist/loleaflet-src.js \
+		$(LOLEAFLET_PREFIX)/dist/bundle.js \
 		--output $@
 endif
 
diff --git a/loleaflet/bundle.js.m4 b/loleaflet/bundle.js.m4
new file mode 100644
index 000000000..b43b74589
--- /dev/null
+++ b/loleaflet/bundle.js.m4
@@ -0,0 +1,22 @@
+m4_changequote([,])m4_dnl
+m4_dnl# foreachq(x, `item_1, item_2, ..., item_n', stmt)
+m4_dnl# quoted list, alternate improved version
+m4_define([m4_foreachq],[m4_ifelse([$2],[],[],[m4_pushdef([$1])_$0([$1],[$3],[],$2)m4_popdef([$1])])])m4_dnl
+m4_define([_m4_foreachq],[m4_ifelse([$#],[3],[],[m4_define([$1],[$4])$2[]$0([$1],[$2],m4_shift(m4_shift(m4_shift($@))))])])m4_dnl
+m4_define([m4_trim],[m4_patsubst([$1],[^. ?\(.*\) .$])])m4_dnl
+m4_dnl
+m4_dnl files for IOS
+m4_ifelse(m4_trim(L10N_IOS_ALL_JS),[],[],[m4_syscmd([cat ]L10N_IOS_ALL_JS)])
+
+m4_dnl node_modules
+m4_foreachq([fileNode],[NODE_MODULES_JS],[
+m4_syscmd([cat ]fileNode)
+])
+
+m4_dnl imported libraries
+m4_foreachq([fileLib],[LOLEAFLET_LIBS_JS],[
+m4_syscmd([cat ]fileLib)
+])
+
+m4_dnl bundled loleaflet
+m4_syscmd([cat ]LOLEAFLET_JS)
commit d9a23d96688d736203f75fdf3fed3d26d742c226
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Mon Mar 2 17:15:38 2020 +0100
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Tue Mar 3 16:00:30 2020 +0100

    parallel-cypress: improve console output of parallel test run.
    
    Change-Id: I29440bafdba101c3f4e86de7d587330b972289de

diff --git a/cypress_test/Makefile.am b/cypress_test/Makefile.am
index 27845aeea..13a769f08 100644
--- a/cypress_test/Makefile.am
+++ b/cypress_test/Makefile.am
@@ -8,6 +8,7 @@ GET_PORT_BINARY = $(abs_builddir)/node_modules/get-port-cli/cli.js
 NPM_INSTALLED = $(abs_builddir)/workdir/npm_installed
 
 PID_FILE=$(abs_builddir)/workdir/loolwsd.pid
+ERROR_LOG=$(abs_builddir)/workdir/error.log
 
 DESKTOP_USER_AGENT = "cypress"
 DESKTOP_TEST_FOLDER = $(abs_srcdir)/integration_tests/desktop
@@ -57,18 +58,23 @@ MOBILE_TEST_FILES_DONE= \
 DESKTOP_TEST_FILES_DONE= \
 	$(foreach test_file,$(DEKSTOP_TEST_FILES),$(DESKTOP_TRACK_FOLDER)/$(test_file).done)
 
-check-local: $(DESKTOP_TEST_FILES_DONE) $(MOBILE_TEST_FILES_DONE)
+check-local: do-check
+	$(if $(wildcard $(ERROR_LOG)),$(error Cypress test failure!))
+
+do-check: $(DESKTOP_TEST_FILES_DONE) $(MOBILE_TEST_FILES_DONE)
 	@$(KILL_COMMAND) || true
 	$(if $(HEADLESS_BUILD), at pkill Xvfb,)
+	$(if $(wildcard $(ERROR_LOG)), at cat $(ERROR_LOG))
 
 $(PID_FILE): @JAILS_PATH@ $(NODE_BINS)
+	@rm -f $(ERROR_LOG)
 	$(call run_JS_error_check)
 	$(if $(HEADLESS_BUILD),$(call start_Xvfb),)
 	$(call start_loolwsd)
 
 $(MOBILE_TEST_FILES_DONE): $(PID_FILE)
 	$(if $(PARALLEL_BUILD),\
-		$(call run_mobile_tests,$(subst $(MOBILE_TRACK_FOLDER)/,,$(basename $@))),\
+		$(call run_mobile_tests,$(subst $(MOBILE_TRACK_FOLDER)/,,$(basename $@)),$(basename $@).log),\
 		$(call run_mobile_tests))
 	$(if $(PARALLEL_BUILD),\
 		@mkdir -p $(dir $@) && touch $@\
@@ -78,7 +84,7 @@ $(MOBILE_TEST_FILES_DONE): $(PID_FILE)
 
 $(DESKTOP_TEST_FILES_DONE): $(PID_FILE)
 	$(if $(PARALLEL_BUILD),\
-		$(call run_desktop_tests,$(subst $(DESKTOP_TRACK_FOLDER)/,,$(basename $@))),\
+		$(call run_desktop_tests,$(subst $(DESKTOP_TRACK_FOLDER)/,,$(basename $@)),$(basename $@).log),\
 		$(call run_desktop_tests))
 	$(if $(PARALLEL_BUILD),\
 		@mkdir -p $(dir $@) && touch $@\
@@ -162,8 +168,12 @@ define run_desktop_tests
 			--headless \
 			--env DATA_FOLDER=$(DESKTOP_DATA_FOLDER),WORKDIR=$(DESKTOP_WORKDIR),WSD_VERSION_HASH=$(LOOLWSD_VERSION_HASH),SERVER_PORT=$(FREE_PORT) \
 			$(if $(1), --spec=$(abs_dir)/integration_tests/desktop/$(1)) \
-			|| ($(KILL_COMMAND) && false))
-	$(if $(PARALLEL_BUILD), at echo "`echo $(RUN_COMMAND) && $(RUN_COMMAND)`",$(RUN_COMMAND))
+			$(if $(PARALLEL_BUILD),,|| ($(KILL_COMMAND) && false)))
+	$(if $(PARALLEL_BUILD),\
+		$(call execute_run_parallel,$(RUN_COMMAND),$(2))\
+	,\
+		$(RUN_COMMAND)\
+	)
 endef
 
 define run_mobile_tests
@@ -175,8 +185,21 @@ define run_mobile_tests
 			--headless \
 			--env DATA_FOLDER=$(MOBILE_DATA_FOLDER),WORKDIR=$(MOBILE_WORKDIR),WSD_VERSION_HASH=$(LOOLWSD_VERSION_HASH),SERVER_PORT=$(FREE_PORT) \
 			$(if $(1), --spec=$(abs_dir)/integration_tests/mobile/$(1)) \
-			|| ($(KILL_COMMAND) && false))
-	$(if $(PARALLEL_BUILD), at echo "`echo $(RUN_COMMAND) && $(RUN_COMMAND)`",$(RUN_COMMAND))
+			$(if $(PARALLEL_BUILD),,|| ($(KILL_COMMAND) && false)))
+	$(if $(PARALLEL_BUILD),\
+		$(call execute_run_parallel,$(RUN_COMMAND),$(2))\
+	,\
+		$(RUN_COMMAND)\
+	)
+endef
+
+define execute_run_parallel
+	@mkdir -p $(dir $(2)) && touch $(2) && \
+	echo "`echo $(1) && $(1)`" > $(2) 2>&1 && \
+	if [ `grep -o "CypressError" $(2)` ];\
+		then cat $(2) >> $(ERROR_LOG);\
+		else cat $(2);\
+	fi;
 endef
 
 NODE_BINS = \
commit 033e353e9dbe435e55091fa0eed1cc6100d29f6d
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Fri Feb 28 14:37:56 2020 +0100
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Tue Mar 3 12:41:54 2020 +0100

    parallel-cypress: restore original sequential build.
    
    Running all tests in one command will display a better output
    and also might be faster.
    
    Change-Id: Ib061fad28eeb1246c774bfb7fd958c3f5b9c317b

diff --git a/cypress_test/Makefile.am b/cypress_test/Makefile.am
index fe37e3c47..27845aeea 100644
--- a/cypress_test/Makefile.am
+++ b/cypress_test/Makefile.am
@@ -57,7 +57,7 @@ MOBILE_TEST_FILES_DONE= \
 DESKTOP_TEST_FILES_DONE= \
 	$(foreach test_file,$(DEKSTOP_TEST_FILES),$(DESKTOP_TRACK_FOLDER)/$(test_file).done)
 
-check-local: $(MOBILE_TEST_FILES_DONE) $(DESKTOP_TEST_FILES_DONE)
+check-local: $(DESKTOP_TEST_FILES_DONE) $(MOBILE_TEST_FILES_DONE)
 	@$(KILL_COMMAND) || true
 	$(if $(HEADLESS_BUILD), at pkill Xvfb,)
 
@@ -67,14 +67,24 @@ $(PID_FILE): @JAILS_PATH@ $(NODE_BINS)
 	$(call start_loolwsd)
 
 $(MOBILE_TEST_FILES_DONE): $(PID_FILE)
-	$(call run_mobile_tests,$(subst $(MOBILE_TRACK_FOLDER),,$(basename $@)))
-	@mkdir -p $(dir $@)
-	@touch $@
+	$(if $(PARALLEL_BUILD),\
+		$(call run_mobile_tests,$(subst $(MOBILE_TRACK_FOLDER)/,,$(basename $@))),\
+		$(call run_mobile_tests))
+	$(if $(PARALLEL_BUILD),\
+		@mkdir -p $(dir $@) && touch $@\
+		,\
+		@$(foreach done_file,$(MOBILE_TEST_FILES_DONE),mkdir -p $(dir $(done_file)) && touch $(done_file) &&) true\
+	)
 
 $(DESKTOP_TEST_FILES_DONE): $(PID_FILE)
-	$(call run_desktop_tests,$(subst $(DESKTOP_TRACK_FOLDER),,$(basename $@)))
-	@mkdir -p $(dir $@)
-	@touch $@
+	$(if $(PARALLEL_BUILD),\
+		$(call run_desktop_tests,$(subst $(DESKTOP_TRACK_FOLDER)/,,$(basename $@))),\
+		$(call run_desktop_tests))
+	$(if $(PARALLEL_BUILD),\
+		@mkdir -p $(dir $@) && touch $@\
+		,\
+		@$(foreach done_file,$(DESKTOP_TEST_FILES_DONE),mkdir -p $(dir $(done_file)) && touch $(done_file) &&) true\
+	)
 
 check-desktop: @JAILS_PATH@ $(NODE_BINS)
 	$(call run_JS_error_check)
commit e18b7ca425cbc6dc949ec280bb916ecdfed057f6
Author:     Yunusemre Şentürk <yunusemre at collabora.com>
AuthorDate: Fri Feb 28 19:17:08 2020 +0300
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Tue Mar 3 12:41:54 2020 +0100

    Wait until Xvfb says it is ready
    
    Change-Id: I3af4428802d6e0ee380536e8dbad1de8aa6d9337
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/89727
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Tamás Zolnai <tamas.zolnai at collabora.com>

diff --git a/cypress_test/Makefile.am b/cypress_test/Makefile.am
index 701fcf093..fe37e3c47 100644
--- a/cypress_test/Makefile.am
+++ b/cypress_test/Makefile.am
@@ -139,7 +139,7 @@ endef
 define start_Xvfb
 	@echo "Launching Xvfb..."
 	Xvfb :$(DISPLAY_NUMBER) &
-	sleep 10
+	while [ ! -f /tmp/.X$(DISPLAY_NUMBER)-lock ] ; do sleep 1; done
 	@echo
 endef
 
commit 2cfd990e75d3d81da7460428fe8f550c1330673b
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Tue Feb 25 13:09:31 2020 +0100
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Tue Mar 3 12:41:54 2020 +0100

    parallel-cypress: Start own Xvfb in CI environment.
    
    It's not necessary when we build with one job, but when
    there are more jobs they try to start their own Xvfb
    and they might use the same DISPLAY number for that
    which leads to a failure or a freeze.
    See also:
    https://docs.cypress.io/guides/guides/continuous-integration.html#Xvfb
    
    Change-Id: I2fb301b1d8e889b2898d6792fb38cece303129a6

diff --git a/cypress_test/Makefile.am b/cypress_test/Makefile.am
index 22c1e0271..701fcf093 100644
--- a/cypress_test/Makefile.am
+++ b/cypress_test/Makefile.am
@@ -24,6 +24,9 @@ MOBILE_TRACK_FOLDER=$(abs_builddir)/workdir/track/mobile
 ALLOWED_PORTS = $(shell seq 9900 1 9980)
 KILL_COMMAND=pkill -F $(PID_FILE) || pkill --signal SIGKILL -F $(PID_FILE)
 PARALLEL_BUILD = $(findstring jobserver,$(MAKEFLAGS))
+DISPLAY_NUMBER = 100
+HEADLESS_BUILD := $(findstring Command failed,$(shell xhost > /dev/null 2>&1 || echo "Command failed, so we are in a headless environment."))
+export DISPLAY=$(if $(HEADLESS_BUILD),:$(DISPLAY_NUMBER),$(shell echo $$DISPLAY))
 
 if HAVE_LO_PATH
 MOBILE_TEST_FILES= \
@@ -56,9 +59,11 @@ DESKTOP_TEST_FILES_DONE= \
 
 check-local: $(MOBILE_TEST_FILES_DONE) $(DESKTOP_TEST_FILES_DONE)
 	@$(KILL_COMMAND) || true
+	$(if $(HEADLESS_BUILD), at pkill Xvfb,)
 
 $(PID_FILE): @JAILS_PATH@ $(NODE_BINS)
 	$(call run_JS_error_check)
+	$(if $(HEADLESS_BUILD),$(call start_Xvfb),)
 	$(call start_loolwsd)
 
 $(MOBILE_TEST_FILES_DONE): $(PID_FILE)
@@ -130,6 +135,14 @@ define start_loolwsd
 	@echo
 endef
 
+#https://docs.cypress.io/guides/guides/continuous-integration.html#Xvfb
+define start_Xvfb
+	@echo "Launching Xvfb..."
+	Xvfb :$(DISPLAY_NUMBER) &
+	sleep 10
+	@echo
+endef
+
 define run_desktop_tests
 	@echo $(if $(1),"Running cypress desktop test: $(1)","Running cypress desktop tests...")
 	@echo
commit eb3b3e54d40599410cafd3da92f33d66d1c850c1
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Sat Feb 22 11:48:33 2020 +0100
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Tue Mar 3 12:41:54 2020 +0100

    parallel-cypress: during parallel build don't write out execution line-by-line
    
    Because in this case, the output of the different threads will be
    mixed in the command line.
    For sequential build keep the original behavior.
    
    Change-Id: Ibf9bf7c6111f8b5b0cf188fab66b97dedd613c65

diff --git a/cypress_test/Makefile.am b/cypress_test/Makefile.am
index 0984e5248..22c1e0271 100644
--- a/cypress_test/Makefile.am
+++ b/cypress_test/Makefile.am
@@ -23,6 +23,7 @@ MOBILE_TRACK_FOLDER=$(abs_builddir)/workdir/track/mobile
 
 ALLOWED_PORTS = $(shell seq 9900 1 9980)
 KILL_COMMAND=pkill -F $(PID_FILE) || pkill --signal SIGKILL -F $(PID_FILE)
+PARALLEL_BUILD = $(findstring jobserver,$(MAKEFLAGS))
 
 if HAVE_LO_PATH
 MOBILE_TEST_FILES= \
@@ -130,27 +131,29 @@ define start_loolwsd
 endef
 
 define run_desktop_tests
+	@echo $(if $(1),"Running cypress desktop test: $(1)","Running cypress desktop tests...")
 	@echo
-	@echo "Running cypress desktop tests..."
-	@echo
-	$(CYPRESS_BINARY) run --browser $(CHROME) \
+	$(eval RUN_COMMAND = \
+		$(CYPRESS_BINARY) run --browser $(CHROME) \
 			--config integrationFolder=$(DESKTOP_TEST_FOLDER),userAgent=$(DESKTOP_USER_AGENT) \
 			--headless \
 			--env DATA_FOLDER=$(DESKTOP_DATA_FOLDER),WORKDIR=$(DESKTOP_WORKDIR),WSD_VERSION_HASH=$(LOOLWSD_VERSION_HASH),SERVER_PORT=$(FREE_PORT) \
 			$(if $(1), --spec=$(abs_dir)/integration_tests/desktop/$(1)) \
-			|| ($(KILL_COMMAND) && false)
+			|| ($(KILL_COMMAND) && false))
+	$(if $(PARALLEL_BUILD), at echo "`echo $(RUN_COMMAND) && $(RUN_COMMAND)`",$(RUN_COMMAND))
 endef
 
 define run_mobile_tests
+	@echo $(if $(1),"Running cypress mobile test: $(1)","Running cypress mobile tests...")
 	@echo
-	@echo "Running cypress mobile tests..."
-	@echo
-	$(CYPRESS_BINARY) run --browser $(CHROME) \
+	$(eval RUN_COMMAND = \
+		$(CYPRESS_BINARY) run --browser $(CHROME) \
 			--config integrationFolder=$(MOBILE_TEST_FOLDER),userAgent=$(MOBILE_USER_AGENT) \
 			--headless \
 			--env DATA_FOLDER=$(MOBILE_DATA_FOLDER),WORKDIR=$(MOBILE_WORKDIR),WSD_VERSION_HASH=$(LOOLWSD_VERSION_HASH),SERVER_PORT=$(FREE_PORT) \
 			$(if $(1), --spec=$(abs_dir)/integration_tests/mobile/$(1)) \
-			|| ($(KILL_COMMAND) && false)
+			|| ($(KILL_COMMAND) && false))
+	$(if $(PARALLEL_BUILD), at echo "`echo $(RUN_COMMAND) && $(RUN_COMMAND)`",$(RUN_COMMAND))
 endef
 
 NODE_BINS = \
commit be9277d7009ba4d432cadd720451ac9606cecf94
Author:     Tamás Zolnai <tamas.zolnai at collabora.com>
AuthorDate: Thu Feb 20 22:01:27 2020 +0100
Commit:     Tamás Zolnai <tamas.zolnai at collabora.com>
CommitDate: Tue Mar 3 12:41:46 2020 +0100

    parallel-cypress: initial parallelization
    
    - Run all spec files separately so we can run them
    in parallel.
    
    Change-Id: I521feeac6e1ac4c3d2c87e989c11d4231945e568

diff --git a/cypress_test/Makefile.am b/cypress_test/Makefile.am
index e05ddbb82..0984e5248 100644
--- a/cypress_test/Makefile.am
+++ b/cypress_test/Makefile.am
@@ -13,24 +13,62 @@ DESKTOP_USER_AGENT = "cypress"
 DESKTOP_TEST_FOLDER = $(abs_srcdir)/integration_tests/desktop
 DESKTOP_DATA_FOLDER = $(abs_srcdir)/data/desktop/
 DESKTOP_WORKDIR = $(abs_builddir)/workdir/desktop/
+DESKTOP_TRACK_FOLDER=$(abs_builddir)/workdir/track/desktop
 
 MOBILE_USER_AGENT = "cypress mobile"
 MOBILE_TEST_FOLDER = $(abs_srcdir)/integration_tests/mobile
 MOBILE_DATA_FOLDER = $(abs_srcdir)/data/mobile/
 MOBILE_WORKDIR = $(abs_builddir)/workdir/mobile/
+MOBILE_TRACK_FOLDER=$(abs_builddir)/workdir/track/mobile
 
 ALLOWED_PORTS = $(shell seq 9900 1 9980)
-FREE_PORT=$(shell $(GET_PORT_BINARY) --host=127.0.0.1 $(ALLOWED_PORTS))
-
 KILL_COMMAND=pkill -F $(PID_FILE) || pkill --signal SIGKILL -F $(PID_FILE)
 
 if HAVE_LO_PATH
-check-local: @JAILS_PATH@ $(NODE_BINS)
+MOBILE_TEST_FILES= \
+	calc/calc_focus_spec.js \
+	impress/impress_focus_spec.js \
+	writer/apply_font_spec.js \
+	writer/apply_paragraph_properties_spec.js \
+	writer/bottom_toolbar_spec.js \
+	writer/focus_spec.js \
+	writer/insert_field_spec.js \
+	writer/insert_formatting_mark_spec.js \
+	writer/insert_object_spec.js \
+	writer/mobile_wizard_state_spec.js \
+	writer/shape_properties_spec.js \
+	writer/spellchecking_spec.js \
+	writer/styles_spec.js \
+	writer/table_properties_spec.js \
+	writer/toolbar_spec.js
+
+DEKSTOP_TEST_FILES= \
+	copy_paste_spec.js \
+	example_desktop_test_spec.js \
+	shape_operations_spec.js
+
+MOBILE_TEST_FILES_DONE= \
+	$(foreach test_file,$(MOBILE_TEST_FILES),$(MOBILE_TRACK_FOLDER)/$(test_file).done)
+
+DESKTOP_TEST_FILES_DONE= \
+	$(foreach test_file,$(DEKSTOP_TEST_FILES),$(DESKTOP_TRACK_FOLDER)/$(test_file).done)
+
+check-local: $(MOBILE_TEST_FILES_DONE) $(DESKTOP_TEST_FILES_DONE)
+	@$(KILL_COMMAND) || true
+
+$(PID_FILE): @JAILS_PATH@ $(NODE_BINS)
 	$(call run_JS_error_check)
 	$(call start_loolwsd)
-	$(call run_desktop_tests)
-	$(call run_mobile_tests)
-	@$(KILL_COMMAND) || true
+
+$(MOBILE_TEST_FILES_DONE): $(PID_FILE)
+	$(call run_mobile_tests,$(subst $(MOBILE_TRACK_FOLDER),,$(basename $@)))
+	@mkdir -p $(dir $@)
+	@touch $@
+
+$(DESKTOP_TEST_FILES_DONE): $(PID_FILE)
+	$(call run_desktop_tests,$(subst $(DESKTOP_TRACK_FOLDER),,$(basename $@)))
+	@mkdir -p $(dir $@)
+	@touch $@
 
 check-desktop: @JAILS_PATH@ $(NODE_BINS)
 	$(call run_JS_error_check)
@@ -73,6 +111,7 @@ define run_JS_error_check
 endef
 
 define start_loolwsd
+	$(eval FREE_PORT:=$(shell $(GET_PORT_BINARY) --host=127.0.0.1 $(ALLOWED_PORTS)))
 	@echo "Found available port for testing: $(FREE_PORT)"
 	@echo
 	@echo "Launching loolwsd..."
@@ -82,9 +121,10 @@ define start_loolwsd
 			--o:child_root_path="@JAILS_PATH@" --o:storage.filesystem[@allow]=true \
 			--disable-ssl \
 			--o:admin_console.username=admin --o:admin_console.password=admin \
-			--o:logging.file[@enable]=true --o:logging.level=trace > /dev/null 2>&1 \
+			--o:logging.file[@enable]=true --o:logging.level=trace \
 			--port=$(FREE_PORT) \
-			--pidfile=$(PID_FILE) &
+			--pidfile=$(PID_FILE) \
+			 > /dev/null 2>&1 &
 	@$(WAIT_ON_BINARY) http://localhost:$(FREE_PORT) --timeout 60000
 	@echo
 endef


More information about the Libreoffice-commits mailing list