[Libreoffice-commits] online.git: Branch 'feature/proxyhack' - 101 commits - android/app android/lib android/templates common/Log.cpp common/Log.hpp common/Seccomp.cpp common/Session.cpp configure.ac cypress_test/data cypress_test/integration_tests cypress_test/Makefile.am fuzzer/ClientSession.cpp ios/Mobile loleaflet/admin loleaflet/css loleaflet/.eslintrc loleaflet/html loleaflet/images loleaflet/js loleaflet/l10n loleaflet/Makefile.am loleaflet/po loleaflet/src Makefile.am net/Socket.hpp net/WebSocketHandler.hpp test/countloolkits.hpp test/DeltaTests.cpp test/fakesockettest.cpp test/helpers.hpp test/httpcrashtest.cpp test/httpwstest.cpp test/integration-http-server.cpp test/lokassert.hpp test/Makefile.am test/TileCacheTests.cpp test/TileQueueTests.cpp test/UnitAdmin.cpp test/UnitBadDocLoad.cpp test/UnitCalc.cpp test/UnitClose.cpp test/UnitCursor.cpp test/UnitEachView.cpp test/UnitHosting.cpp test/UnitHTTP.cpp test/UnitInsertDelete.cpp test/UnitLargePaste.cpp test/UnitLoad.cpp test/UnitLoadTort ure.cpp test/UnitOAuth.cpp test/UnitPasswordProtected.cpp test/UnitPaste.cpp test/UnitRenderingOptions.cpp test/UnitRenderShape.cpp test/UnitSession.cpp test/UnitTiffLoad.cpp test/UnitUNOCommand.cpp test/UnitWOPI.cpp test/UnitWOPIRenameFile.cpp test/UnitWOPISaveAs.cpp test/UnitWOPITemplate.cpp test/WhiteBoxTests.cpp test/WopiTestServer.hpp wsd/ClientSession.cpp wsd/ClientSession.hpp wsd/DocumentBroker.cpp wsd/DocumentBroker.hpp wsd/FileServer.cpp wsd/LOOLWSD.cpp wsd/LOOLWSD.hpp wsd/ProxyProtocol.cpp wsd/ProxyProtocol.hpp

Thu, 19 Mar 2020 08:57:26 -0700

Rebased ref, commits from common ancestor:
commit 172cdcafc6c0306740c544dd0841786cd6c55133
Author:     Michael Meeks <michael.me...@collabora.com>
AuthorDate: Thu Mar 19 15:54:28 2020 +0000
Commit:     Michael Meeks <michael.me...@collabora.com>
CommitDate: Thu Mar 19 15:54:28 2020 +0000

    Proxy protocol bits.
    
    For now very silly: hex length\r\n + content.
    
    Change-Id: I256b834a23cca975a705da2c569887665ac6be02

diff --git a/wsd/DocumentBroker.hpp b/wsd/DocumentBroker.hpp
index dd84960d1..cb5bf54e3 100644
--- a/wsd/DocumentBroker.hpp
+++ b/wsd/DocumentBroker.hpp
@@ -258,7 +258,7 @@ public:
         const Poco::URI& uriPublic,
         const bool isReadOnly,
         const std::string& hostNoTrust,
-        const std::shared_ptr<Socket> &moveSocket);
+        const std::shared_ptr<StreamSocket> &socket);
 
     /// 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 683197879..69bf3357e 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -2855,7 +2855,7 @@ private:
                             {
                                 docBroker->handleProxyRequest(
                                     sessionId, id, uriPublic, isReadOnly,
-                                    hostNoTrust, moveSocket);
+                                    hostNoTrust, streamSocket);
                                 return;
                             }
                             catch (const UnauthorizedRequestException& exc)
diff --git a/wsd/ProxyProtocol.cpp b/wsd/ProxyProtocol.cpp
index 41043a57a..0f875ff72 100644
--- a/wsd/ProxyProtocol.cpp
+++ b/wsd/ProxyProtocol.cpp
@@ -25,7 +25,7 @@ void DocumentBroker::handleProxyRequest(
     const Poco::URI& uriPublic,
     const bool isReadOnly,
     const std::string& hostNoTrust,
-    const std::shared_ptr<Socket> &socket)
+    const std::shared_ptr<StreamSocket> &socket)
 {
     std::shared_ptr<ClientSession> clientSession;
     if (sessionId == "fetchsession")
@@ -37,6 +37,22 @@ void DocumentBroker::handleProxyRequest(
         addSession(clientSession);
         LOOLWSD::checkDiskSpaceAndWarnClients(true);
         LOOLWSD::checkSessionLimitsAndWarnClients();
+
+        LOG_TRC("Returning id " << clientSession->getId());
+
+        std::ostringstream oss;
+        oss << "HTTP/1.1 200 OK\r\n"
+            "Last-Modified: " << Util::getHttpTimeNow() << "\r\n"
+            "User-Agent: " WOPI_AGENT_STRING "\r\n"
+            "Content-Length: " << clientSession->getId().size() << "\r\n"
+            "Content-Type: application/json\r\n"
+            "X-Content-Type-Options: nosniff\r\n"
+            "\r\n"
+            << clientSession->getId();
+
+        socket->send(oss.str());
+        socket->shutdown();
+        return;
     }
     else
     {
@@ -78,4 +94,60 @@ void ProxyProtocolHandler::handleRequest(const std::string 
&uriPublic,
     (void)socket;
 }
 
+int ProxyProtocolHandler::sendTextMessage(const char *msg, const size_t len, 
bool flush) const
+{
+    LOG_TRC("ProxyHack - send text msg " + std::string(msg, len));
+
+    // FIXME: need some sort of framing [!]
+    _outQueue.insert(_outQueue.end(), msg, msg + len);
+    auto sock = popWriteSocket();
+    if (sock && flush)
+        flushQueueTo(sock);
+    return len;
+}
+
+int ProxyProtocolHandler::sendBinaryMessage(const char *data, const size_t 
len, bool flush) const
+{
+    LOG_TRC("ProxyHack - send binary msg len " << len);
+    _outQueue.insert(_outQueue.end(), data, data + len);
+    auto sock = popWriteSocket();
+    if (sock && flush)
+        flushQueueTo(sock);
+    return len;
+}
+
+void ProxyProtocolHandler::shutdown(bool goingAway = false, const std::string 
&statusMessage = "")
+{
+    LOG_TRC("ProxyHack - shutdown " << goingAway << ": " << statusMessage);
+}
+
+void ProxyProtocolHandler::getIOStats(uint64_t &sent, uint64_t &recv)
+{
+    sent = recv = 0;
+}
+
+void ProxyProtocolHandler::dumpState(std::ostream& os)
+{
+    os << "proxy protocol\n";
+}
+
+void ProxyProtocolHandler::flushQueueTo(const std::shared_ptr<StreamSocket> 
&socket)
+{
+}
+
+// LRU-ness ...
+std::shared_ptr<StreamSocket> ProxyProtocolHandler::popWriteSocket()
+{
+    std::weak_ptr<StreamSocket> sock;
+    while (!_writeSockets.empty())
+    {
+        sock = _writeSockets.front();
+        _writeSockets.erase(_writeSockets.begin());
+        auto realSock = sock.lock();
+        if (realSock)
+            return realSock;
+    }
+    return std::shared_ptr<StreamSocket>();
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/wsd/ProxyProtocol.hpp b/wsd/ProxyProtocol.hpp
index 1f88e1fa7..9278b7536 100644
--- a/wsd/ProxyProtocol.hpp
+++ b/wsd/ProxyProtocol.hpp
@@ -30,9 +30,9 @@ public:
     }
 
     /// Called after successful socket reads.
-    void handleIncomingMessage(SocketDisposition &/* disposition */) override
+    void handleIncomingMessage(SocketDisposition &/* disposition */) override;
     {
-        assert("we get our data a different way" && false);
+        assert("we get our data a different way - in incoming requests" && 
false);
     }
 
     int getPollEvents(std::chrono::steady_clock::time_point /* now */,
@@ -59,40 +59,22 @@ public:
     /// Clear all external references
     virtual void dispose() { _msgHandler.reset(); }
 
-    int sendTextMessage(const char *msg, const size_t len, bool flush = false) 
const override
-    {
-        LOG_TRC("ProxyHack - send text msg " + std::string(msg, len));
-        (void) flush;
-        return len;
-    }
-
-    int sendBinaryMessage(const char *data, const size_t len, bool flush = 
false) const override
-    {
-        (void) data; (void) flush;
-        LOG_TRC("ProxyHack - send binary msg len " << len);
-        return len;
-    }
-
-    void shutdown(bool goingAway = false, const std::string &statusMessage = 
"") override
-    {
-        LOG_TRC("ProxyHack - shutdown " << goingAway << ": " << statusMessage);
-    }
-
-    void getIOStats(uint64_t &sent, uint64_t &recv) override
-    {
-        sent = recv = 0;
-    }
-
-    void dumpState(std::ostream& os)
-    {
-        os << "proxy protocol\n";
-    }
+    int sendTextMessage(const char *msg, const size_t len, bool flush = false) 
const override;
+    int sendBinaryMessage(const char *data, const size_t len, bool flush = 
false) const override;
+    void shutdown(bool goingAway = false, const std::string &statusMessage = 
"") override;
+    void getIOStats(uint64_t &sent, uint64_t &recv) override;
+    void dumpState(std::ostream& os);
 
     void handleRequest(const std::string &uriPublic,
                        const std::shared_ptr<Socket> &socket);
 
 private:
-    std::vector<std::weak_ptr<StreamSocket>> _sockets;
+    std::shared_ptr<StreamSocket> popWriteSocket();
+    void flushQueueTo(const std::shared_ptr<StreamSocket> &socket);
+
+    std::vector<std::weak_ptr<StreamSocket>> _writeSockets;
+    /// queue things when we have no socket to hand.
+    std::vector<char> _outQueue;
 };
 
 #endif
commit 5e20f38ff40eb66c157f9493334dcc859d9e5449
Author:     Michael Meeks <michael.me...@collabora.com>
AuthorDate: Wed Mar 4 13:54:04 2020 +0000
Commit:     Michael Meeks <michael.me...@collabora.com>
CommitDate: Thu Mar 19 15:20:59 2020 +0000

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

diff --git a/Makefile.am b/Makefile.am
index e4e6ed5db..92d87fa50 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -112,6 +112,7 @@ loolwsd_sources = common/Crypto.cpp \
                   wsd/AdminModel.cpp \
                   wsd/Auth.cpp \
                   wsd/DocumentBroker.cpp \
+                  wsd/ProxyProtocol.cpp \
                   wsd/LOOLWSD.cpp \
                   wsd/ClientSession.cpp \
                   wsd/FileServer.cpp \
@@ -203,6 +204,7 @@ wsd_headers = wsd/Admin.hpp \
               wsd/Auth.hpp \
               wsd/ClientSession.hpp \
               wsd/DocumentBroker.hpp \
+              wsd/ProxyProtocol.hpp \
               wsd/Exceptions.hpp \
               wsd/FileServer.hpp \
               wsd/LOOLWSD.hpp \
diff --git a/loleaflet/js/global.js b/loleaflet/js/global.js
index a08c4cf3b..74e982873 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;
@@ -200,7 +281,8 @@
                                window.postMobileError(log);
                        } else if (global.socket && (global.socket instanceof 
WebSocket) && global.socket.readyState === 1) {
                                global.socket.send(log);
-                       } else if (global.socket && (global.socket instanceof 
global.L.Socket) && global.socket.connected()) {
+                       } else if (global.socket && global.L && global.L.Socket 
&&
+                                  (global.socket instanceof global.L.Socket) 
&& global.socket.connected()) {
                                global.socket.sendMessage(log);
                        } else {
                                var req = new XMLHttpRequest();
@@ -275,7 +357,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 b4118479f..f4a44fd06 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/net/Socket.hpp b/net/Socket.hpp
index a6395b9b4..2290dadd9 100644
--- a/net/Socket.hpp
+++ b/net/Socket.hpp
@@ -446,6 +446,11 @@ public:
         }
     }
 
+    std::shared_ptr<ProtocolHandlerInterface> getProtocol() const
+    {
+        return _protocol;
+    }
+
     /// Do we have something to send ?
     virtual bool hasQueuedMessages() const = 0;
     /// Please send them to me then.
diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp
index b47285dd0..e347dbd74 100644
--- a/wsd/ClientSession.hpp
+++ b/wsd/ClientSession.hpp
@@ -37,9 +37,6 @@ public:
     void construct();
     virtual ~ClientSession();
 
-    /// Lookup any session by id.
-    static std::shared_ptr<ClientSession> getById(const std::string &id);
-
     void setReadOnly() override;
 
     enum SessionState {
@@ -288,7 +285,6 @@ private:
     std::chrono::steady_clock::time_point _viewLoadStart;
 };
 
-
 #endif
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/wsd/DocumentBroker.hpp b/wsd/DocumentBroker.hpp
index 68369d274..dd84960d1 100644
--- a/wsd/DocumentBroker.hpp
+++ b/wsd/DocumentBroker.hpp
@@ -251,6 +251,15 @@ public:
         const bool isReadOnly,
         const std::string& hostNoTrust);
 
+    /// Find or create a new client session for the PHP proxy
+    void handleProxyRequest(
+        const std::string& sessionId,
+        const std::string& id,
+        const Poco::URI& uriPublic,
+        const bool isReadOnly,
+        const std::string& hostNoTrust,
+        const std::shared_ptr<Socket> &moveSocket);
+
     /// 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 177360dbc..683197879 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -238,6 +238,9 @@ namespace
 #if ENABLE_SUPPORT_KEY
 inline void shutdownLimitReached(const 
std::shared_ptr<ProtocolHandlerInterface>& proto)
 {
+    if (!proto)
+        return;
+
     const std::string error = Poco::format(PAYLOAD_UNAVAILABLE_LIMIT_REACHED, 
LOOLWSD::MaxDocuments, LOOLWSD::MaxConnections);
     LOG_INF("Sending client 'hardlimitreached' message: " << error);
 
@@ -1764,9 +1767,12 @@ static std::shared_ptr<DocumentBroker>
         if (docBroker->isMarkedToDestroy())
         {
             LOG_WRN("DocBroker with docKey [" << docKey << "] that is marked 
to be destroyed. Rejecting client request.");
-            std::string msg("error: cmd=load kind=docunloading");
-            proto->sendTextMessage(msg.data(), msg.size());
-            proto->shutdown(true, "error: cmd=load kind=docunloading");
+            if (proto)
+            {
+                std::string msg("error: cmd=load kind=docunloading");
+                proto->sendTextMessage(msg.data(), msg.size());
+                proto->shutdown(true, "error: cmd=load kind=docunloading");
+            }
             return nullptr;
         }
     }
@@ -1782,9 +1788,12 @@ static std::shared_ptr<DocumentBroker>
     }
 
     // Indicate to the client that we're connecting to the docbroker.
-    const std::string statusConnect = "statusindicator: connect";
-    LOG_TRC("Sending to Client [" << statusConnect << "].");
-    proto->sendTextMessage(statusConnect.data(), statusConnect.size());
+    if (proto)
+    {
+        const std::string statusConnect = "statusindicator: connect";
+        LOG_TRC("Sending to Client [" << statusConnect << "].");
+        proto->sendTextMessage(statusConnect.data(), statusConnect.size());
+    }
 
     if (!docBroker)
     {
@@ -2242,6 +2251,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")
                 {
@@ -2775,6 +2791,99 @@ 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;
+            }
+        }
+
+        const std::string hostNoTrust = (LOOLWSD::ServerName.empty() ? 
request.getHost() : LOOLWSD::ServerName);
+
+        LOG_INF("URL [" << LOOLWSD::anonymizeUrl(url) << "] is " << 
(isReadOnly ? "readonly" : "writable") << ".");
+        (void)request; (void)message; (void)disposition;
+
+        std::shared_ptr<ProtocolHandlerInterface> none;
+        // Request a kit process for this doc.
+        std::shared_ptr<DocumentBroker> docBroker = findOrCreateDocBroker(
+            none, url, docKey, _id, uriPublic);
+        if (docBroker)
+        {
+            // need to move into the DocumentBroker context before doing 
session lookup / creation etc.
+            std::string id = _id;
+            disposition.setMove([docBroker, id, uriPublic, isReadOnly, 
hostNoTrust, sessionId]
+                                (const std::shared_ptr<Socket> &moveSocket)
+                {
+                    LOG_TRC("Setting up docbroker thread for " << 
docBroker->getDocKey());
+                    // 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, id, uriPublic, 
isReadOnly, hostNoTrust, sessionId, moveSocket]()
+                        {
+                            // Now inside the document broker thread ...
+                            LOG_TRC("In the docbroker thread for " << 
docBroker->getDocKey());
+
+                            auto streamSocket = 
std::static_pointer_cast<StreamSocket>(moveSocket);
+                            try
+                            {
+                                docBroker->handleProxyRequest(
+                                    sessionId, id, uriPublic, isReadOnly,
+                                    hostNoTrust, moveSocket);
+                                return;
+                            }
+                            catch (const UnauthorizedRequestException& exc)
+                            {
+                                LOG_ERR("Unauthorized Request while loading 
session for " << docBroker->getDocKey() << ": " << exc.what());
+                            }
+                            catch (const StorageConnectionException& exc)
+                            {
+                                LOG_ERR("Error while loading : " << 
exc.what());
+                            }
+                            catch (const std::exception& exc)
+                            {
+                                LOG_ERR("Error while loading : " << 
exc.what());
+                            }
+                            // badness occured:
+                            std::ostringstream oss;
+                            oss << "HTTP/1.1 400\r\n"
+                                << "Date: " << Util::getHttpTimeNow() << "\r\n"
+                                << "User-Agent: LOOLWSD WOPI Agent\r\n"
+                                << "Content-Length: 0\r\n"
+                                << "\r\n";
+                            streamSocket->send(oss.str());
+                            streamSocket->shutdown();
+                        });
+                });
+        }
+    }
+
     void handleClientWsUpgrade(const Poco::Net::HTTPRequest& request, const 
std::string& url,
                                SocketDisposition &disposition)
     {
@@ -2837,7 +2946,7 @@ private:
             // Request a kit process for this doc.
             std::shared_ptr<DocumentBroker> docBroker = findOrCreateDocBroker(
                 std::static_pointer_cast<ProtocolHandlerInterface>(ws), url, 
docKey, _id, uriPublic);
-             if (docBroker)
+            if (docBroker)
             {
 #if MOBILEAPP
                 const std::string hostNoTrust;
diff --git a/wsd/ProxyProtocol.cpp b/wsd/ProxyProtocol.cpp
new file mode 100644
index 000000000..41043a57a
--- /dev/null
+++ b/wsd/ProxyProtocol.cpp
@@ -0,0 +1,81 @@
+/* -*- 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 <config.h>
+
+#include "DocumentBroker.hpp"
+#include "ClientSession.hpp"
+#include "ProxyProtocol.hpp"
+#include "Exceptions.hpp"
+#include "LOOLWSD.hpp"
+#include <Socket.hpp>
+
+#include <atomic>
+#include <cassert>
+
+void DocumentBroker::handleProxyRequest(
+    const std::string& sessionId,
+    const std::string& id,
+    const Poco::URI& uriPublic,
+    const bool isReadOnly,
+    const std::string& hostNoTrust,
+    const std::shared_ptr<Socket> &socket)
+{
+    std::shared_ptr<ClientSession> clientSession;
+    if (sessionId == "fetchsession")
+    {
+        LOG_TRC("Create session for " << _docKey);
+        clientSession = createNewClientSession(
+                std::make_shared<ProxyProtocolHandler>(),
+                id, uriPublic, isReadOnly, hostNoTrust);
+        addSession(clientSession);
+        LOOLWSD::checkDiskSpaceAndWarnClients(true);
+        LOOLWSD::checkSessionLimitsAndWarnClients();
+    }
+    else
+    {
+        LOG_TRC("Find session for " << _docKey << " with id " << sessionId);
+        for (const auto &it : _sessions)
+        {
+            if (it.second->getId() == sessionId)
+            {
+                clientSession = it.second;
+                break;
+            }
+        }
+        if (!clientSession)
+        {
+            LOG_ERR("Invalid session id used " << sessionId);
+            throw BadRequestException("invalid session id");
+        }
+    }
+
+    auto protocol = clientSession->getProtocol();
+    auto streamSocket = std::static_pointer_cast<StreamSocket>(socket);
+    streamSocket->setHandler(protocol);
+
+    // this DocumentBroker's poll handles reading & writing
+    addSocketToPoll(socket);
+
+    auto proxy = std::static_pointer_cast<ProxyProtocolHandler>(
+        protocol);
+
+    proxy->handleRequest(uriPublic.toString(), socket);
+}
+
+void ProxyProtocolHandler::handleRequest(const std::string &uriPublic,
+                                         const std::shared_ptr<Socket> &socket)
+{
+    bool bRead = uriPublic.find("/write") == std::string::npos;
+    LOG_INF("Proxy handle request " << uriPublic << " type: " <<
+            (bRead ? "read" : "write"));
+    (void)socket;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/wsd/ProxyProtocol.hpp b/wsd/ProxyProtocol.hpp
new file mode 100644
index 000000000..1f88e1fa7
--- /dev/null
+++ b/wsd/ProxyProtocol.hpp
@@ -0,0 +1,100 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_PROXY_PROTOCOL_HPP
+#define INCLUDED_PROXY_PROTOCOL_HPP
+
+#include <net/Socket.hpp>
+
+/// Interface for building a websocket from this ...
+class ProxyProtocolHandler : public ProtocolHandlerInterface
+{
+public:
+    ProxyProtocolHandler()
+    {
+    }
+
+    virtual ~ProxyProtocolHandler()
+    {
+    }
+
+    /// Will be called exactly once by setHandler
+    void onConnect(const std::shared_ptr<StreamSocket>& /* socket */) override
+    {
+    }
+
+    /// Called after successful socket reads.
+    void handleIncomingMessage(SocketDisposition &/* disposition */) override
+    {
+        assert("we get our data a different way" && false);
+    }
+
+    int getPollEvents(std::chrono::steady_clock::time_point /* now */,
+                      int &/* timeoutMaxMs */) override
+    {
+        // underlying buffer based polling is fine.
+        return POLLIN;
+    }
+
+    void checkTimeout(std::chrono::steady_clock::time_point /* now */) override
+    {
+    }
+
+    void performWrites() override
+    {
+    }
+
+    void onDisconnect() override
+    {
+        // connections & sockets come and go a lot.
+    }
+
+public:
+    /// Clear all external references
+    virtual void dispose() { _msgHandler.reset(); }
+
+    int sendTextMessage(const char *msg, const size_t len, bool flush = false) 
const override
+    {
+        LOG_TRC("ProxyHack - send text msg " + std::string(msg, len));
+        (void) flush;
+        return len;
+    }
+
+    int sendBinaryMessage(const char *data, const size_t len, bool flush = 
false) const override
+    {
+        (void) data; (void) flush;
+        LOG_TRC("ProxyHack - send binary msg len " << len);
+        return len;
+    }
+
+    void shutdown(bool goingAway = false, const std::string &statusMessage = 
"") override
+    {
+        LOG_TRC("ProxyHack - shutdown " << goingAway << ": " << statusMessage);
+    }
+
+    void getIOStats(uint64_t &sent, uint64_t &recv) override
+    {
+        sent = recv = 0;
+    }
+
+    void dumpState(std::ostream& os)
+    {
+        os << "proxy protocol\n";
+    }
+
+    void handleRequest(const std::string &uriPublic,
+                       const std::shared_ptr<Socket> &socket);
+
+private:
+    std::vector<std::weak_ptr<StreamSocket>> _sockets;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
commit 406d3c609574918eaa5fd6f50726008d9e2d1073
Author:     Michael Meeks <michael.me...@collabora.com>
AuthorDate: Wed Mar 4 13:52:51 2020 +0000
Commit:     Michael Meeks <michael.me...@collabora.com>
CommitDate: Thu Mar 19 14:25:13 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 ce9e12756..2b53999c8 100644
--- a/wsd/FileServer.cpp
+++ b/wsd/FileServer.cpp
@@ -595,6 +595,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)
 {
@@ -641,15 +652,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";
@@ -658,16 +675,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
 
@@ -850,13 +867,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 
&nbsp;&nbsp;<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
@@ -874,7 +893,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 d19e85f9482054f8589aedee88df0f04aa76476d
Author:     Henry Castro <hcas...@collabora.com>
AuthorDate: Thu Mar 19 09:51:05 2020 -0400
Commit:     Andras Timar <andras.ti...@collabora.com>
CommitDate: Thu Mar 19 15:01:11 2020 +0100

    loleaflet: check the cell cursor width to scroll
    
    This is an improvement of the commit 
569b342c2029876b1c9ef8b54f235138bff9c792,
    It is better to check the cell cursor width instead of spacing to scroll
    the view when very large merge cell occurs
    
    Change-Id: I049cda34f886738ce9fbd3776113a219c5bd038f
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90751
    Tested-by: Andras Timar <andras.ti...@collabora.com>
    Reviewed-by: Andras Timar <andras.ti...@collabora.com>

diff --git a/loleaflet/src/layer/tile/TileLayer.js 
b/loleaflet/src/layer/tile/TileLayer.js
index a398ff1bc..61a5b688b 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -2864,12 +2864,13 @@ L.TileLayer = L.GridLayer.extend({
                                }
                                else if (horizontalDirection !== 0 || 
verticalDirection != 0) {
                                        var mapX = Math.abs(mapBounds.getEast() 
- mapBounds.getWest());
-                                       var spacingX = 
Math.abs(this._cellCursor.getEast() - this._cellCursor.getWest()) / 4.0;
+                                       var cursorX = 
Math.abs(this._cellCursor.getEast() - this._cellCursor.getWest());
+                                       var spacingX = cursorX / 4.0;
                                        var spacingY = 
Math.abs((this._cellCursor.getSouth() - this._cellCursor.getNorth())) / 4.0;
 
                                        if (this._cellCursor.getWest() < 
mapBounds.getWest()) {
                                                scrollX = 
this._cellCursor.getWest() - mapBounds.getWest() - spacingX;
-                                       } else if (spacingX < mapX && 
this._cellCursor.getEast() > mapBounds.getEast()) {
+                                       } else if (cursorX < mapX && 
this._cellCursor.getEast() > mapBounds.getEast()) {
                                                scrollX = 
this._cellCursor.getEast() - mapBounds.getEast() + spacingX;
                                        }
                                        if (this._cellCursor.getNorth() > 
mapBounds.getNorth()) {
commit b659e8613cb491e3e621d2eac7614d3fb4b691b9
Author:     Pedro Pinto Silva <pedro.si...@collabora.com>
AuthorDate: Thu Mar 19 14:00:45 2020 +0100
Commit:     Pedro Pinto da Silva <pedro.si...@collabora.com>
CommitDate: Thu Mar 19 14:50:13 2020 +0100

    MobileWizard: fix table design  options and chart options by
    
    updating css rules according to the latest html structure
    
    Change-Id: I373e185a444e8f5db3096cd05c9f47f470ef73c3
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90747
    Tested-by: Pedro Pinto da Silva <pedro.si...@collabora.com>
    Reviewed-by: Pedro Pinto da Silva <pedro.si...@collabora.com>

diff --git a/loleaflet/css/mobilewizard.css b/loleaflet/css/mobilewizard.css
index 99695142b..bb6addd51 100644
--- a/loleaflet/css/mobilewizard.css
+++ b/loleaflet/css/mobilewizard.css
@@ -614,10 +614,20 @@
                float:left;
        }
        #MergeCells > span{
-               display: none;
+               color: #4d82b8;
+               text-align: end;
+               vertical-align: bottom;
+       }
+       #DeleteTable{
+               background-color: #fbf4f5;
+               border-radius: 6px;
+               padding-right: 4% !important;
        }
        #DeleteTable > span{
-               display: none;
+               font-weight: bold;
+               color: #e68497;
+               text-align: end;
+               vertical-align: bottom;
        }
        .colorcontainer {
                width: 120px;
@@ -728,7 +738,7 @@
                vertical-align: baseline;
                padding-left: 24px;
        }
-       div[title='Table Design'] div[id^=Use]{
+       #SdTableDesignPanel div[id^=Use]{
                width: 92%;
                padding-right: 4% !important;
        }
commit 3b1ca68a212329111938189bc3df90a5c8996c57
Author:     Tamás Zolnai <tamas.zol...@collabora.com>
AuthorDate: Wed Mar 18 13:18:59 2020 +0100
Commit:     Tamás Zolnai <tamas.zol...@collabora.com>
CommitDate: Thu Mar 19 14:48:42 2020 +0100

    cypress: mobile: add spellchecking tests for impress.
    
    Change-Id: I7b3909c79ca1fad27c5e3754a36f38f659e467e1
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90712
    Tested-by: Tamás Zolnai <tamas.zol...@collabora.com>
    Reviewed-by: Tamás Zolnai <tamas.zol...@collabora.com>

diff --git a/cypress_test/Makefile.am b/cypress_test/Makefile.am
index 603ec9269..394627af7 100644
--- a/cypress_test/Makefile.am
+++ b/cypress_test/Makefile.am
@@ -36,6 +36,7 @@ MOBILE_TEST_FILES= \
        calc/insertion_wizard_spec.js \
        calc/spellchecking_spec.js \
        impress/impress_focus_spec.js \
+       impress/spellchecking_spec.js \
        writer/apply_font_spec.js \
        writer/apply_paragraph_properties_spec.js \
        writer/bottom_toolbar_spec.js \
diff --git a/cypress_test/data/mobile/impress/spellchecking.odp 
b/cypress_test/data/mobile/impress/spellchecking.odp
new file mode 100644
index 000000000..af6096b7d
Binary files /dev/null and b/cypress_test/data/mobile/impress/spellchecking.odp 
differ
diff --git a/cypress_test/integration_tests/common/helper.js 
b/cypress_test/integration_tests/common/helper.js
index dea956fa5..02a4cbdda 100644
--- a/cypress_test/integration_tests/common/helper.js
+++ b/cypress_test/integration_tests/common/helper.js
@@ -92,8 +92,9 @@ function assertCursorAndFocus() {
 }
 
 // Select all text via CTRL+A shortcut.
-function selectAllText() {
-       assertCursorAndFocus();
+function selectAllText(assertFocus = true) {
+       if (assertFocus)
+               assertCursorAndFocus();
 
        cy.log('Select all text');
 
diff --git a/cypress_test/integration_tests/common/impress.js 
b/cypress_test/integration_tests/common/impress.js
index 6e1e59c55..00064c096 100644
--- a/cypress_test/integration_tests/common/impress.js
+++ b/cypress_test/integration_tests/common/impress.js
@@ -1,4 +1,4 @@
-/* global cy require */
+/* global cy require expect*/
 
 var helper = require('./helper');
 
@@ -61,6 +61,39 @@ function typeTextAndVerify(text, expected) {
        helper.expectTextForClipboard(expected);
 }
 
+function copyShapeContentToClipboard() {
+       // TODO: this fails on assertHaveKeyboardInput()
+       // assertInTextEditMode();
+
+       helper.selectAllText(false);
+
+       // Open context menu
+       cy.get('.leaflet-marker-icon')
+               .then(function(marker) {
+                       expect(marker).to.have.lengthOf(2);
+                       var XPos = (marker[0].getBoundingClientRect().right + 
marker[1].getBoundingClientRect().left) / 2;
+                       var YPos = marker[0].getBoundingClientRect().top - 5;
+                       helper.longPressOnDocument(XPos, YPos);
+               });
+
+       cy.get('#mobile-wizard')
+               .should('be.visible');
+
+       // Execute copy
+       cy.get('.ui-header.level-0.mobile-wizard.ui-widget .context-menu-link 
.menu-entry-with-icon', {timeout : 10000})
+               .contains('Copy')
+               .click();
+
+       // Close warning about clipboard operations
+       cy.get('.vex-dialog-button-primary.vex-dialog-button.vex-first')
+               .click();
+
+       // Wait until it's closed
+       cy.get('.vex-overlay')
+               .should('not.exist');
+}
+
 module.exports.assertNotInTextEditMode = assertNotInTextEditMode;
 module.exports.assertInTextEditMode = assertInTextEditMode;
 module.exports.typeTextAndVerify = typeTextAndVerify;
+module.exports.copyShapeContentToClipboard = copyShapeContentToClipboard;
diff --git 
a/cypress_test/integration_tests/mobile/impress/spellchecking_spec.js 
b/cypress_test/integration_tests/mobile/impress/spellchecking_spec.js
new file mode 100644
index 000000000..8831e3555
--- /dev/null
+++ b/cypress_test/integration_tests/mobile/impress/spellchecking_spec.js
@@ -0,0 +1,118 @@
+/* global describe it cy beforeEach require afterEach expect*/
+
+var helper = require('../../common/helper');
+var impress = require('../../common/impress');
+
+describe('Spell checking menu.', function() {
+       beforeEach(function() {
+               helper.beforeAllMobile('spellchecking.odp', 'impress');
+
+               // Click on edit button
+               helper.enableEditingMobile();
+       });
+
+       afterEach(function() {
+               helper.afterAll('spellchecking.odp');
+       });
+
+       function openContextMenu() {
+               // Click on the center of the slide to step into text edit mode
+               cy.get('#document-container')
+                       .then(function(items) {
+                               expect(items).to.have.length(1);
+                               var XPos = 
(items[0].getBoundingClientRect().left + 
items[0].getBoundingClientRect().right) / 2;
+                               var YPos = 
(items[0].getBoundingClientRect().top + 
items[0].getBoundingClientRect().bottom) / 2;
+                               cy.get('body')
+                                       .dblclick(XPos, YPos);
+                       });
+
+               cy.get('.leaflet-cursor.blinking-cursor')
+                       .should('exist');
+
+               helper.selectAllText(false);
+
+               // Open context menu
+               cy.get('.leaflet-marker-icon')
+                       .then(function(markers) {
+                               expect(markers.length).to.have.greaterThan(1);
+                               for (var i = 0; i < markers.length; i++) {
+                                       if 
(markers[i].classList.contains('leaflet-selection-marker-start')) {
+                                               var startPos = 
markers[i].getBoundingClientRect();
+                                       } else if 
(markers[i].classList.contains('leaflet-selection-marker-end')) {
+                                               var endPos = 
markers[i].getBoundingClientRect();
+                                       }
+                               }
+
+                               // Remove selection
+                               cy.get('#document-container')
+                                       .type('{leftarrow}');
+                               cy.get('.leaflet-marker-icon')
+                                       .should('not.exist');
+
+                               var XPos = startPos.right + 10;
+                               var YPos = endPos.top - 10;
+                               helper.longPressOnDocument(XPos, YPos);
+                       });
+
+               cy.get('#mobile-wizard-content')
+                       .should('be.visible');
+       }
+
+       it('Apply suggestion.', function() {
+               openContextMenu();
+
+               cy.get('.context-menu-link')
+                       .contains('hello')
+                       .click();
+
+               impress.copyShapeContentToClipboard();
+
+               cy.get('#copy-paste-container pre')
+                       .then(function(item) {
+                               expect(item).to.have.lengthOf(1);
+                               
expect(item[0].innerText).to.have.string('hello');
+                       });
+       });
+
+       it('Ignore all.', function() {
+               openContextMenu();
+
+               cy.get('.context-menu-link')
+                       .contains('Ignore All')
+                       .click();
+
+               openContextMenu();
+
+               // We don't get the spell check context menu any more
+               cy.get('.context-menu-link')
+                       .contains('Paste');
+       });
+
+       it('Apply language for word.', function() {
+               openContextMenu();
+
+               cy.get('.context-menu-link')
+                       .contains('Word is Finnish')
+                       .click();
+
+               openContextMenu();
+
+               // We don't get the spell check context menu any more
+               cy.get('.context-menu-link')
+                       .contains('Paste');
+       });
+
+       it('Apply language for paragraph.', function() {
+               openContextMenu();
+
+               cy.get('.context-menu-link')
+                       .contains('Paragraph is Finnish')
+                       .click();
+
+               openContextMenu();
+
+               // We don't get the spell check context menu any more
+               cy.get('.context-menu-link')
+                       .contains('Paste');
+       });
+});
commit ff92eb07952d07f000d7738c5990d0ac5718ea45
Author:     Tamás Zolnai <tamas.zol...@collabora.com>
AuthorDate: Thu Mar 19 02:31:52 2020 +0100
Commit:     Tamás Zolnai <tamas.zol...@collabora.com>
CommitDate: Thu Mar 19 13:47:55 2020 +0100

    cypress: better core version detection.
    
    To make it work both with release and debug core
    build.
    Also disable some tests failing with core/cp-6-2.
    
    Change-Id: I5617712e19dc8aaba0c5f9fbdf9c17d9a19fb18b
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90725
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Tamás Zolnai <tamas.zol...@collabora.com>

diff --git a/cypress_test/integration_tests/common/helper.js 
b/cypress_test/integration_tests/common/helper.js
index 5d3c017d9..dea956fa5 100644
--- a/cypress_test/integration_tests/common/helper.js
+++ b/cypress_test/integration_tests/common/helper.js
@@ -177,7 +177,8 @@ function detectLOCoreVersion() {
                cy.get('#lokit-version')
                        .then(function(items) {
                                expect(items).have.lengthOf(1);
-                               if (items[0].textContent.includes('Collabora 
OfficeDev 6.2')) {
+                               if (items[0].textContent.includes('Collabora') 
&&
+                                   items[0].textContent.includes('6.2')) {
                                        Cypress.env('LO_CORE_VERSION', 
'cp-6-2');}
                                else {
                                        Cypress.env('LO_CORE_VERSION', 
'master');
diff --git 
a/cypress_test/integration_tests/mobile/impress/impress_focus_spec.js 
b/cypress_test/integration_tests/mobile/impress/impress_focus_spec.js
index a33bede16..fdd33c569 100644
--- a/cypress_test/integration_tests/mobile/impress/impress_focus_spec.js
+++ b/cypress_test/integration_tests/mobile/impress/impress_focus_spec.js
@@ -80,7 +80,7 @@ describe('Impress focus tests', function() {
                impress.typeTextAndVerify('Bazinga Impress');
        });
 
-       it('Single-click to edit', function() {
+       it.skip('Single-click to edit', function() {
 
                helper.enableEditingMobile();
 
diff --git a/cypress_test/integration_tests/mobile/writer/apply_font_spec.js 
b/cypress_test/integration_tests/mobile/writer/apply_font_spec.js
index 08e51940c..c6f3b4e23 100644
--- a/cypress_test/integration_tests/mobile/writer/apply_font_spec.js
+++ b/cypress_test/integration_tests/mobile/writer/apply_font_spec.js
@@ -204,7 +204,7 @@ describe('Apply font changes.', function() {
                // TODO: Shadowed is not in the clipboard content.
        });
 
-       it('Apply grow.', function() {
+       it.skip('Apply grow.', function() {
                // Push grow
                cy.get('#Growimg')
                        .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 2fcf6f6cf..1bce30aac 100644
--- a/cypress_test/integration_tests/mobile/writer/shape_properties_spec.js
+++ b/cypress_test/integration_tests/mobile/writer/shape_properties_spec.js
@@ -98,7 +98,7 @@ describe('Change shape properties via mobile wizard.', 
function() {
                        .should('have.attr', 'fill', 'rgb(114,159,207)');
        });
 
-       it('Change shape width.', function() {
+       it.skip('Change shape width.', function() {
                // TODO: Entering a value inside the spinbutton has no effect 
on the shape.
                if (Cypress.env('LO_CORE_VERSION') === 'master')
                        return;
commit b76352d8e35fe97f2433de83f563d7a967e2408c
Author:     Tamás Zolnai <tamas.zol...@collabora.com>
AuthorDate: Thu Mar 19 11:23:18 2020 +0100
Commit:     Tamás Zolnai <tamas.zol...@collabora.com>
CommitDate: Thu Mar 19 12:37:23 2020 +0100

    mobile: add a method to generate ID for submenus.
    
    Change-Id: I45c2865573eb6b9d070fc6c36f4f1369e2ac3172
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90739
    Tested-by: Tamás Zolnai <tamas.zol...@collabora.com>
    Reviewed-by: Tamás Zolnai <tamas.zol...@collabora.com>

diff --git a/loleaflet/src/control/Control.JSDialogBuilder.js 
b/loleaflet/src/control/Control.JSDialogBuilder.js
index f0171fb41..e6c8f5eeb 100644
--- a/loleaflet/src/control/Control.JSDialogBuilder.js
+++ b/loleaflet/src/control/Control.JSDialogBuilder.js
@@ -1658,6 +1658,11 @@ L.Control.JSDialogBuilder = L.Control.extend({
        }
 });
 
+L.Control.JSDialogBuilder.generateIDForSubMenu = function(menuStructure) {
+       if (menuStructure['text'] === 'Anchor')
+               menuStructure['id'] = 'submenu_anchor';
+};
+
 L.Control.JSDialogBuilder.getMenuStructureForMobileWizard = function(menu, 
mainMenu, itemCommand) {
        if (itemCommand.includes('sep'))
                return null;
@@ -1706,6 +1711,7 @@ L.Control.JSDialogBuilder.getMenuStructureForMobileWizard 
= function(menu, mainM
                        element = 
this.getMenuStructureForMobileWizard(menu.items[menuItem], false, menuItem);
                        if (element)
                                menuStructure['children'].push(element);
+                       this.generateIDForSubMenu(menuStructure);
                }
        }
 
commit 45886322e72cfce1ba21641cf9211497240e37b8
Author:     Tamás Zolnai <tamas.zol...@collabora.com>
AuthorDate: Wed Mar 18 19:26:40 2020 +0100
Commit:     Tamás Zolnai <tamas.zol...@collabora.com>
CommitDate: Thu Mar 19 12:32:52 2020 +0100

    cypress: mobile: move focus related tests to focus_spec.js.
    
    Change-Id: Ib8719f6d14e578ffc0125077942c063f4b5f3f37
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90718
    Tested-by: Tamás Zolnai <tamas.zol...@collabora.com>
    Reviewed-by: Tamás Zolnai <tamas.zol...@collabora.com>

diff --git 
a/cypress_test/integration_tests/mobile/writer/bottom_toolbar_spec.js 
b/cypress_test/integration_tests/mobile/writer/bottom_toolbar_spec.js
index fd23fd030..a0c484eba 100644
--- a/cypress_test/integration_tests/mobile/writer/bottom_toolbar_spec.js
+++ b/cypress_test/integration_tests/mobile/writer/bottom_toolbar_spec.js
@@ -34,30 +34,6 @@ describe('Pushing bottom toolbar items.', function() {
                        .should('exist');
        });
 
-       it('Apply bold, check keyboard.', function() {
-               cy.get('#document-container')
-                       .type('{downarrow}');
-               helper.selectAllText();
-
-               cy.get('#tb_editbar_item_bold div table')
-                       .should('not.have.class', 'checked');
-
-               cy.window().then(win => {
-                       win.lastInputState = 
win.map._textInput.shouldAcceptInput();
-               });
-
-               cy.get('#tb_editbar_item_bold')
-                       .click();
-
-               cy.get('#tb_editbar_item_bold div table')
-                       .should('have.class', 'checked');
-
-               cy.window().then(win => {
-                       var acceptInput = 
win.map._textInput.shouldAcceptInput();
-                       expect(acceptInput, 'Should accept 
input').to.equal(win.lastInputState);
-               });
-       });
-
        it('Apply italic.', function() {
                cy.get('#tb_editbar_item_italic div table')
                        .should('not.have.class', 'checked');
@@ -73,31 +49,6 @@ describe('Pushing bottom toolbar items.', function() {
                cy.get('#copy-paste-container p i')
                        .should('exist');
        });
-
-       it('Apply italic, check keyboard.', function() {
-               cy.get('#document-container')
-                       .type('{downarrow}');
-               helper.selectAllText();
-
-               cy.get('#tb_editbar_item_italic div table')
-                       .should('not.have.class', 'checked');
-
-               cy.window().then(win => {
-                       win.lastInputState = 
win.map._textInput.shouldAcceptInput();
-               });
-
-               cy.get('#tb_editbar_item_italic')
-                       .click();
-
-               cy.get('#tb_editbar_item_italic div table')
-                       .should('have.class', 'checked');
-
-               cy.window().then(win => {
-                       var acceptInput = 
win.map._textInput.shouldAcceptInput();
-                       expect(acceptInput, 'Should accept 
input').to.equal(win.lastInputState);
-               });
-       });
-
        it('Apply underline.', function() {
                cy.get('#tb_editbar_item_underline div table')
                        .should('not.have.class', 'checked');
@@ -114,30 +65,6 @@ describe('Pushing bottom toolbar items.', function() {
                        .should('exist');
        });
 
-       it('Apply underline, check keyboard.', function() {
-               cy.get('#document-container')
-                       .type('{downarrow}');
-               helper.selectAllText();
-
-               cy.get('#tb_editbar_item_underline div table')
-                       .should('not.have.class', 'checked');
-
-               cy.window().then(win => {
-                       win.lastInputState = 
win.map._textInput.shouldAcceptInput();
-               });
-
-               cy.get('#tb_editbar_item_underline')
-                       .click();
-
-               cy.get('#tb_editbar_item_underline div table')
-                       .should('have.class', 'checked');
-
-               cy.window().then(win => {
-                       var acceptInput = 
win.map._textInput.shouldAcceptInput();
-                       expect(acceptInput, 'Should accept 
input').to.equal(win.lastInputState);
-               });
-       });
-
        it('Apply strikeout.', function() {
                cy.get('#tb_editbar_item_strikeout div table')
                        .should('not.have.class', 'checked');
diff --git a/cypress_test/integration_tests/mobile/writer/focus_spec.js 
b/cypress_test/integration_tests/mobile/writer/focus_spec.js
index f1efae3ee..268324c58 100644
--- a/cypress_test/integration_tests/mobile/writer/focus_spec.js
+++ b/cypress_test/integration_tests/mobile/writer/focus_spec.js
@@ -274,4 +274,91 @@ describe('Focus tests', function() {
                cy.document().its('activeElement.tagName')
                        .should('be.eq', 'BODY');
        });
+
+       it('Apply bold, check keyboard.', function() {
+               // Click on edit button
+               helper.enableEditingMobile();
+
+               // Grab focus to the document
+               cy.get('#document-container')
+                       .type('x');
+
+               helper.selectAllText();
+
+               cy.get('#tb_editbar_item_bold div table')
+                       .should('not.have.class', 'checked');
+
+               cy.window().then(win => {
+                       win.lastInputState = 
win.map._textInput.shouldAcceptInput();
+               });
+
+               cy.get('#tb_editbar_item_bold')
+                       .click();
+
+               cy.get('#tb_editbar_item_bold div table')
+                       .should('have.class', 'checked');
+
+               cy.window().then(win => {
+                       var acceptInput = 
win.map._textInput.shouldAcceptInput();
+                       expect(acceptInput, 'Should accept 
input').to.equal(win.lastInputState);
+               });
+       });
+
+       it('Apply italic, check keyboard.', function() {
+               // Click on edit button
+               helper.enableEditingMobile();
+
+               // Grab focus to the document
+               cy.get('#document-container')
+                       .type('x');
+
+               helper.selectAllText();
+
+               cy.get('#tb_editbar_item_italic div table')
+                       .should('not.have.class', 'checked');
+
+               cy.window().then(win => {
+                       win.lastInputState = 
win.map._textInput.shouldAcceptInput();
+               });
+
+               cy.get('#tb_editbar_item_italic')
+                       .click();
+
+               cy.get('#tb_editbar_item_italic div table')
+                       .should('have.class', 'checked');
+
+               cy.window().then(win => {
+                       var acceptInput = 
win.map._textInput.shouldAcceptInput();
+                       expect(acceptInput, 'Should accept 
input').to.equal(win.lastInputState);
+               });
+       });
+
+       it('Apply underline, check keyboard.', function() {
+               // Click on edit button
+               helper.enableEditingMobile();
+
+               // Grab focus to the document
+               cy.get('#document-container')
+                       .type('x');
+
+               helper.selectAllText();
+
+               cy.get('#tb_editbar_item_underline div table')
+                       .should('not.have.class', 'checked');
+
+               cy.window().then(win => {
+                       win.lastInputState = 
win.map._textInput.shouldAcceptInput();
+               });
+
+               cy.get('#tb_editbar_item_underline')
+                       .click();
+
+               cy.get('#tb_editbar_item_underline div table')
+                       .should('have.class', 'checked');
+
+               cy.window().then(win => {
+                       var acceptInput = 
win.map._textInput.shouldAcceptInput();
+                       expect(acceptInput, 'Should accept 
input').to.equal(win.lastInputState);
+               });
+       });
 });
commit 6e96d4f6dfb8bafd38c846394ce545224164df3b
Author:     Tamás Zolnai <tamas.zol...@collabora.com>
AuthorDate: Wed Mar 18 22:05:36 2020 +0100
Commit:     Tamás Zolnai <tamas.zol...@collabora.com>
CommitDate: Thu Mar 19 12:30:54 2020 +0100

    cypress: catch "Uncaught TypeError" error in the log.
    
    During parallel build.
    
    Change-Id: I18d78250650e5d16ff9a4ff8588a2e955a934f44
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90721
    Tested-by: Tamás Zolnai <tamas.zol...@collabora.com>
    Reviewed-by: Tamás Zolnai <tamas.zol...@collabora.com>

diff --git a/cypress_test/Makefile.am b/cypress_test/Makefile.am
index d712c5296..603ec9269 100644
--- a/cypress_test/Makefile.am
+++ b/cypress_test/Makefile.am
@@ -198,7 +198,7 @@ endef
 define execute_run_parallel
        @mkdir -p $(dir $(2)) && touch $(2) && \
        echo "`echo $(1) && $(1)`" > $(2) 2>&1 && \
-       if [ -z `grep -o -m 1 "CypressError\|AssertionError" $(2)` ];\
+       if [ -z `grep -o -m 1 "CypressError\|AssertionError\|Uncaught 
TypeError" $(2)` ];\
                then cat $(2);\
                else cat $(2) >> $(ERROR_LOG);\
        fi;
commit c1b56a357cf7642d37b7f01fefd1bcaf8217e903
Author:     Tamás Zolnai <tamas.zol...@collabora.com>
AuthorDate: Thu Mar 19 01:46:45 2020 +0100
Commit:     Tamás Zolnai <tamas.zol...@collabora.com>
CommitDate: Thu Mar 19 12:20:03 2020 +0100

    cypress: mobile: don't wait too long during long press.
    
    In some use cases this time was too long and hammerjs
    recognized a pan end by 'pointerup' event. We have
    500 ms time set for long press in loleaflet code,
    so use the exact value here.
    
    Change-Id: Iab47c668ffa591ccfd7d2eeed10f2c3ec5664b89
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90723
    Tested-by: Tamás Zolnai <tamas.zol...@collabora.com>
    Reviewed-by: Tamás Zolnai <tamas.zol...@collabora.com>

diff --git a/cypress_test/integration_tests/common/helper.js 
b/cypress_test/integration_tests/common/helper.js
index d1789530d..5d3c017d9 100644
--- a/cypress_test/integration_tests/common/helper.js
+++ b/cypress_test/integration_tests/common/helper.js
@@ -210,7 +210,8 @@ function longPressOnDocument(posX, posY) {
                                .trigger('pointerdown', eventOptions)
                                .trigger('pointermove', eventOptions);
 
-                       cy.wait(600);
+                       // This value is set in Map.TouchGesture.js.
+                       cy.wait(500);
 
                        cy.get('.leaflet-pane.leaflet-map-pane')
                                .trigger('pointerup', eventOptions);
commit 3418701eccb1933e90f1bb4c5c77efbcb3e7a916
Author:     Tamás Zolnai <tamas.zol...@collabora.com>
AuthorDate: Wed Mar 18 19:28:57 2020 +0100
Commit:     Tamás Zolnai <tamas.zol...@collabora.com>
CommitDate: Thu Mar 19 12:01:18 2020 +0100

    Revert "cypress: calc assert we are in text edit mode in focus test"
    
    There is no assertInTextEditMode() in calc_helper.js.
    
    This reverts commit bc5bf2eb2ad355f342f158bcefcb968bb7e78318.
    
    Change-Id: I97526eb8ea0297a2ea28080f09c85e4284d70932
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90719
    Tested-by: Tamás Zolnai <tamas.zol...@collabora.com>
    Reviewed-by: Tamás Zolnai <tamas.zol...@collabora.com>

diff --git a/cypress_test/integration_tests/mobile/calc/focus_spec.js 
b/cypress_test/integration_tests/mobile/calc/focus_spec.js
index 0ea2b3b77..9b375db34 100644
--- a/cypress_test/integration_tests/mobile/calc/focus_spec.js
+++ b/cypress_test/integration_tests/mobile/calc/focus_spec.js
@@ -69,7 +69,5 @@ describe('Calc focus tests', function() {
                // Document has the focus
                cy.document().its('activeElement.className')
                        .should('be.eq', 'clipboard');
-
-               calcHelper.assertInTextEditMode();
        });
 });
commit b33e97e623c41e3de0f58017249d7be476fd305d
Author:     Tamás Zolnai <tamas.zol...@collabora.com>
AuthorDate: Thu Mar 19 02:05:18 2020 +0100
Commit:     Tamás Zolnai <tamas.zol...@collabora.com>
CommitDate: Thu Mar 19 12:00:42 2020 +0100

    mobile: fix type error hit by cypress test.
    
    The issue comes up when we get a drag-end event
    without a drag-start event.
    Not sure wether this error can happen in real life
    or it's just the cypress test framework mobile emulation
    which can lead to this issue, but an additional check
    won't hurt.
    
    Change-Id: I352a5b523e8400ffc5e77ebefd91166e40517019
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90724
    Tested-by: Tamás Zolnai <tamas.zol...@collabora.com>
    Reviewed-by: Tamás Zolnai <tamas.zol...@collabora.com>

diff --git a/loleaflet/src/layer/vector/Path.Drag.js 
b/loleaflet/src/layer/vector/Path.Drag.js
index 47fd8e0e2..c85a9ac32 100644
--- a/loleaflet/src/layer/vector/Path.Drag.js
+++ b/loleaflet/src/layer/vector/Path.Drag.js
@@ -269,7 +269,7 @@ L.Handler.PathDrag = L.Handler.extend(/** @lends  
L.Path.Drag.prototype */ {
                        this._path._map.dragging.enable();
                }
 
-               if (!this._path.options.manualDrag && !moved) {
+               if (!this._path.options.manualDrag && !moved && 
this._mouseDown) {
                        this._path._map._handleDOMEvent(this._mouseDown);
                        this._path._map._handleDOMEvent(evt);
                }
commit d6fe0979b71ecc864257ff4595d2ffd27cc449d0
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Thu Mar 19 09:12:33 2020 +0100
Commit:     Michael Meeks <michael.me...@collabora.com>
CommitDate: Thu Mar 19 11:50:54 2020 +0100

    common: handle missing protocol in Session::getIOStats()
    
    This sometimes causes unit-integration to fail and now is consistent
    with other member functions of Session that handle missing protocol.
    
    Change-Id: I43c7fcae964cfcb5911ff57d63bd4cb569e6b97c
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90732
    Tested-by: Michael Meeks <michael.me...@collabora.com>
    Reviewed-by: Michael Meeks <michael.me...@collabora.com>

diff --git a/common/Session.cpp b/common/Session.cpp
index d24b615a9..859cd011e 100644
--- a/common/Session.cpp
+++ b/common/Session.cpp
@@ -257,6 +257,12 @@ void Session::handleMessage(const std::vector<char> &data)
 
 void Session::getIOStats(uint64_t &sent, uint64_t &recv)
 {
+    if (!_protocol)
+    {
+        LOG_TRC("ERR - missing protocol " << getName() << ": Get IO stats.");
+        return;
+    }
+
     _protocol->getIOStats(sent, recv);
 }
 
commit 08e18799f0a194f30e1a99641c4277a4ab4bcd73
Author:     Pedro Pinto Silva <pedro.si...@collabora.com>
AuthorDate: Thu Mar 19 10:55:21 2020 +0100
Commit:     Pedro Pinto da Silva <pedro.si...@collabora.com>
CommitDate: Thu Mar 19 11:09:38 2020 +0100

    Mobile: Hamburger and mobileWizard: Add missing icons
    
    Change-Id: Id479cb6f1623630b84583c6d2ddd89c9779db7dd
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90737
    Tested-by: Pedro Pinto da Silva <pedro.si...@collabora.com>
    Reviewed-by: Pedro Pinto da Silva <pedro.si...@collabora.com>

diff --git a/loleaflet/images/lc_changesmenu.svg 
b/loleaflet/images/lc_changesmenu.svg
new file mode 100644
index 000000000..088300cc5
--- /dev/null
+++ b/loleaflet/images/lc_changesmenu.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg";><path d="m5 
3v18h6.267578c-.795872-1.135833-1.267578-2.513707-1.267578-4 0-3.854149 
3.145851-7 7-7 .694626 0 1.365658.106777 2 .296875v-3.5683594l-4-3.7285156z" 
fill="#fff"/><path d="m5 2c-.554 0-1 .446-1 1v18c0 .554.446 1 1 
1h7.115234c-.312835-.305717-.595999-.640846-.847656-1h-6.267578v-18h9v4c0 
.554.446 1 1 1h4v2.296875c.346387.103805.678167.236716 1 
.390625v-3.4375-.2089844c0-.4506799.098038-.4254121-.605469-1.0664062l-3.30664-3.2382813c-.709525-.7368575-.710169-.7363281-1.09375-.7363281h-.332032-.662109z"
 fill="#808080"/><path d="m15 7h4l-4-4z" fill="#fff" fill-rule="evenodd"/><rect 
fill="#808080" height="1" ry=".5" width="5" x="7" y="5"/><rect fill="#808080" 
height="1" ry=".5" width="3" x="7" y="9"/><rect fill="#d65532" height="1" 
ry=".5" width="5" x="12" y="9"/><path d="m7.5 12c-.277 
0-.5.223-.5.5s.223.5.5.5h3.767578c.251657-.359154.534821-.694283.847656-1z" 
fill="#d65532"/><path d="m7.5 15c-.277 0-.5.223-.5.5s.223.5
 .5.5h2.580078c.049324-.341059.119834-.676443.216797-1z" fill="#808080"/><path 
d="m12.474609 15c-.262915 0-.474609.211694-.474609.474609v.05078c0 
.262917.211694.474611.474609.474611h1.050782c.06573 0 
.128731-.01316.185547-.03711.000848-.000358.0011-.0016.002-.002l.248047-.248047c.000362-.000847.0016-.0011.002-.002.023857-.056721.037015-.119724.037015-.185452v-.05078c0-.262917-.211694-.474611-.474609-.474611z"
 fill="#808080"/><rect fill="#d65532" height="1" ry=".474576" width="2" x="7" 
y="18"/><path d="m17 11a6 6 0 0 0 -6 6 6 6 0 0 0 6 6 6 6 0 0 0 6-6 6 6 0 0 0 
-6-6zm0 1a5 5 0 0 1 5 5 5 5 0 0 1 -5 5 5 5 0 0 1 -5-5 5 5 0 0 1 5-5z" 
fill="#808080"/><circle cx="17" cy="17" fill="#fff" r="5"/><circle cx="17" 
cy="17" fill="#d65532" r="2"/></svg>
\ No newline at end of file
diff --git a/loleaflet/images/lc_datamenu.svg b/loleaflet/images/lc_datamenu.svg
new file mode 100644
index 000000000..06251b16f
--- /dev/null
+++ b/loleaflet/images/lc_datamenu.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg";><path d="m4.71875 
2.5058594-1.2128906 1.1855468v1.1875.3613282l5.9941406 
8.3828126v4.310547.046875l2.878906 2.02539 1.003906-2.005859h-.382812a1.0001 
1.0001 0 0 1 -.78125-1.625l2.964844-3.699219 
5.310547-7.4355466v-1.5488282l-1.212891-1.1855468z" fill="#fff"/><path 
d="m17.004237 12h5.995763l-4 4h2l-9 7 3-6h-2z" fill="#eac282" 
fill-rule="evenodd"/><path d="m4.71875 2a.50535904.50535904 0 0 0 
-.3535156.1445312l-1.2128906 1.1855469a.50535904.50535904 0 0 0 
-.1523438.3613281v1.1875.3613282a.50535904.50535904 0 0 0 
.09375.2949218l5.9003906 8.2499998v4.148438.046875a.50535904.50535904 0 0 0 
.2148438.414062l2.9414066 
2.070313.457031-.916016-2.601563-1.830078v-4.095703a.50535904.50535904 0 0 0 
-.0957028-.294922l-5.9003906-8.25v-.1992188-.9746093l.9160156-.8945313h14.1484378l.916015.8945313v1.1738281l-5.894531
 8.25a.50535904.50535904 0 0 0 -.095703.294922v.529297l2.224609-2.777344a1.0001 
1.0001 0 0 1 .777344-.375l3.904297-5.4648438a
 .50535904.50535904 0 0 0 .09375-.2949218v-1.5488282a.50535904.50535904 0 0 0 
-.152344-.3613281l-1.21289-1.1855469a.50535904.50535904 0 0 0 
-.353516-.1445312zm10.292969 17.212891-.146485.292968.146485-.113281z" 
fill="#808080"/></svg>
\ No newline at end of file
diff --git a/loleaflet/images/lc_diagramtype.svg 
b/loleaflet/images/lc_diagramtype.svg
new file mode 100644
index 000000000..ee97da3a6
--- /dev/null
+++ b/loleaflet/images/lc_diagramtype.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg";><rect 
fill="#4d82b8" height="15" ry="1" width="5" x="2" y="7"/><path d="m17 10c-.554 
0-1 .446-1 1v5.585938l5-5v-.585938c0-.554-.446-1-1-1z" fill="#808080"/><path 
d="m10 2c-.554 0-1 .446-1 1v18c0 .554.446 1 1 
1h.585938l3.414062-3.414062v-15.585938c0-.554-.446-1-1-1z" 
fill="#eac282"/><path d="m23 11-12 12h12zm-2 5v5h-5z" fill="#4d82b8" 
fill-rule="evenodd"/></svg>
\ No newline at end of file
diff --git a/loleaflet/images/lc_downloadas.svg 
b/loleaflet/images/lc_downloadas.svg
new file mode 100644
index 000000000..462f994fe
--- /dev/null
+++ b/loleaflet/images/lc_downloadas.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg";><path d="m5 
4v17h12.076172c.08822-.229036.224537-.442298.402344-.601562l.40625-.408204h-6.363282c-.420168.005943-.867654-.182805-1.138672-.484375-.271015-.30157-.382812-.666177-.382812-1.015625
 0-.349447.111796-.714055.382812-1.015625.271018-.30157.718504-.490275 
1.138672-.484375h6.351563l-.421875-.421875c-.452852-.438274-.556085-1.125408-.347656-1.640625.207997-.514149.758145-.9356
 
1.386718-.9375h.001954c.175227-.000445.345797.035698.507812.09375v-6.3554684l-4-3.7285156z"
 fill="#fff"/><path d="m5 3c-.554 0-1 .446-1 1v17c0 .554.446 1 1 
1h12.060547c-.047908-.135592-.083757-.278117-.089844-.435547-.007198-.186166.034536-.380297.105469-.564453h-12.076172v-17h9v4c0
 .554.446 1 1 
1h4v5.083984c.211956.075948.408045.193521.568359.357422l.431641.431641v-6.623047-.2089844c0-.4506799.098038-.4254121-.605469-1.0664062l-3.30664-3.2382813c-.709525-.7368575-.710169-.7363281-1.09375-.7363281h-.332032-.662109z"
 fill="#808080"/><g fill-rule=
 "evenodd"><path d="m15 8h4l-4-4z" fill="#fff"/><path d="m18.494141 
14.990234a.50005.50005 0 0 0 -.347657.859375l2.144532 
2.140625h-8.783204a.50005.50005 0 1 0 0 1h8.78711l-2.148438 
2.152344a.50005.50005 0 1 0 .707032.705078l2.957031-2.960937a.50005.50005 0 0 0 
-.003906-.796875l-2.953125-2.949219a.50005.50005 0 0 0 -.359375-.150391z" 
fill="#4d82b8"/></g></svg> 
diff --git a/loleaflet/images/lc_editmenu.svg b/loleaflet/images/lc_editmenu.svg
new file mode 100644
index 000000000..cf0c07abc
--- /dev/null
+++ b/loleaflet/images/lc_editmenu.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg";><path d="m5 
3v18h5.462891l.517578-2.103516c.04344-.177395.134629-.339507.263672-.46875l5.529297-5.523437.0078-.0078.738281-.720703
 1.480481-1.476575v-3.9707034l-4-3.7285156zm14 16.1875-1.802734 
1.8125h1.802734z" fill="#fff"/><path d="m5 2c-.554 0-1 .446-1 1v18c0 .554.446 1 
1 1h5.216797l.246094-1h-5.462891v-18h9v4c0 .554.446 1 1 
1h4v2.699219l.189453-.189453c.10637-.106241.227363-.190073.353516-.263672.022042-.012592.043911-.023576.066406-.035156.123752-.06524.251663-.116414.384766-.150391.00198-.000505.003877-.001455.005859-.001953v-2.808594-.2089844c0-.4506799.098038-.4254121-.605469-1.0664062l-3.30664-3.2382813c-.709525-.7368575-.710169-.7363281-1.09375-.7363281h-.332032-.662109zm15
 16.179688-1 1.005859v.001953 1.8125h-1.802734l-.992188 1h2.794922c.554 0 
1-.446 1-1z" fill="#808080"/><path d="m15 7h4l-4-4z" fill="#fff" 
fill-rule="evenodd"/><g fill="#eac282" transform="translate(-1 -1)"><path 
d="m18.480469 14.611328-5.529297
  5.523438-.951172 3.865234 3.806641-1.011719 5.509765-5.544922z"/><path 
d="m21.419922 12c-.189562 0-.378184.07172-.523438.216797l-1.673828 1.669922 
2.847656 2.849609 1.710938-1.638672c.290506-.290127.290506-.756747 
0-1.046875l-1.835938-1.833984c-.145254-.145064-.335828-.216797-.52539-.216797z"/></g></svg>
\ No newline at end of file
diff --git a/loleaflet/images/lc_file.svg b/loleaflet/images/lc_file.svg
new file mode 100644
index 000000000..0e617d96f
--- /dev/null
+++ b/loleaflet/images/lc_file.svg
@@ -0,0 +1,7 @@
+<svg version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg";>
+ <path d="m5 4v17s14-0.92018 14-13.271l-4-3.7285z" fill="#fff"/>
+ <path d="m5 3c-0.554 0-1 0.446-1 1v17c0 0.554 0.446 1 1 1h2.6074 9.4531 
1.9434c0.554 0 1-0.446 1-1v-12.479l-0.003906 
0.0039062v-0.27539-0.20898c0-0.45068 
0.098038-0.42541-0.60547-1.0664l-3.3066-3.2383c-0.70952-0.73686-0.71017-0.73633-1.0938-0.73633h-0.33203-0.66211-9zm0
 1h9v4c0 0.554 0.446 1 1 1h4v5.084c0.001376 4.93e-4 0.002531 0.001457 0.003906 
0.001954v6.9141h-1.9277-8.5508-3.5254v-17z" fill="#808080"/>
+ <g fill-rule="evenodd">
+  <path d="m15 8h4l-4-4z" fill="#fff"/>
+ </g>
+</svg>
diff --git a/loleaflet/images/lc_pagesetup.svg 
b/loleaflet/images/lc_pagesetup.svg
new file mode 100644
index 000000000..e0d41c5d6
--- /dev/null
+++ b/loleaflet/images/lc_pagesetup.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg";><path d="m9 
4.9980469v3.0019531c0 .554-.446 1-1 
1h-4.9980469v9.998047h14.2714849l3.728515-4v-10.0000001z" fill="#fff"/><path 
d="m9 3.9980469v1h12.001953v9.0000001h-4c-.554 0-1 .446-1 
1v4h-12.9999999v-9.998047h-1v9.998047c0 .554.446 1 1 1h10.1093749 
3.642578.207032c.450679 0 .427365.098038 
1.068359-.605469l3.236328-3.30664c.736858-.709525.736328-.712123.736328-1.095704v-.330078-.662109-9.0000001c0-.554-.446-1-1-1z"
 fill="#808080"/><path d="m17.00238 14.99762v4l4-4z" fill="#fff" 
fill-rule="evenodd"/><g fill="#eac282"><rect height="1" ry=".5" width="6" x="1" 
y="1"/><rect height="6" ry=".5" width="1" x="1" y="1"/><path d="m2.4999352 
7.4999941 5.0000589-5.0000589v5.0000589z" fill-rule="evenodd" stroke="#eac282" 
stroke-linecap="round" stroke-linejoin="round" 
stroke-width="1.000012"/></g></svg>
\ No newline at end of file
diff --git a/loleaflet/images/lc_sheetmenu.svg 
b/loleaflet/images/lc_sheetmenu.svg
new file mode 100644
index 000000000..f19ef8747
--- /dev/null
+++ b/loleaflet/images/lc_sheetmenu.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg";><path d="m3 
3h18v18h-18z" fill="#fff"/><path d="m3 2c-.554 0-1 .446-1 1v18c0 .554.446 1 1 
1h18c.554 0 1-.446 1-1v-18c0-.554-.446-1-1-1zm0 1h5v6h-5zm6 0h6v6h-6zm7 
0h5v6h-5zm-13 7h5v3h-5zm6 0h6v3h-6zm7 0h5v3h-5zm-13 4h5v3h-5zm6 0h6v3h-6zm7 
0h5v3h-5zm-13 4h5v3h-5zm6 0h6v3h-6zm7 0h5v3h-5z" fill="#808080"/><path d="m2 
2h20v4h-20z" fill="#4d82b8"/></svg>
\ No newline at end of file
diff --git a/loleaflet/images/lc_slidemenu.svg 
b/loleaflet/images/lc_slidemenu.svg
new file mode 100644
index 000000000..03c7196fb
--- /dev/null
+++ b/loleaflet/images/lc_slidemenu.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg";><path d="m3 
7h18v13h-18z" fill="#fff"/><path d="m2 6v15h1 8.585938 9.414062 
1v-10.414062-3.585938-1zm1 1h18v4.585938 8.414062h-8.414062-9.585938z" 
fill="#808080"/><path d="m1 3h22v2h-22z" fill="#4d82b8"/><path d="m5 
9h14v2h-14z" fill="#eac282"/><g fill="#808080"><path d="m5 12h6v1h-6z"/><path 
d="m5 16h6v1h-6z"/><path d="m5 14h6v1h-6z"/><path d="m5 18h6v1h-6z"/><path 
d="m13 12h6v1h-6z"/><path d="m13 16h6v1h-6z"/><path d="m13 14h6v1h-6z"/><path 
d="m13 18h6v1h-6z"/></g></svg>
\ No newline at end of file
diff --git a/loleaflet/images/lc_tabledesign.svg 
b/loleaflet/images/lc_tabledesign.svg
new file mode 100644
index 000000000..c3c697518
--- /dev/null
+++ b/loleaflet/images/lc_tabledesign.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg";><path d="m3 
3v18h8.585938l9.414062-9.414062v-8.585938zm17 15.414062-1.585938 
1.585938h1.585938z" fill="#fff"/><path d="m21 2c.554 0 1 .446 1 1v7.585938l-1 
1v-1.585938h-5v3h3.585937l-1 1h-2.585937v2.585938l-1.414062 
1.414062h-5.585938v3h2.585938l-1 1h-7.585938c-.554 
0-1-.446-1-1v-18c0-.554.446-1 1-1zm0 1h-5v6h5zm-6 0h-6v6h6zm-7 0h-5v6h5zm7 
7h-6v3h6zm-7 0h-5v3h5zm7 4h-6v3h6zm-7 0h-5v3h5zm0 4h-5v3h5z" 
fill="#808080"/><path d="m-22 2h20v4h-20z" fill="#808080" transform="scale(-1 
1)"/><path d="m23 11-12 12h12zm-2 5v5h-5z" fill="#4d82b8" 
fill-rule="evenodd"/></svg>
\ No newline at end of file
diff --git a/loleaflet/images/lc_tablemenu.svg 
b/loleaflet/images/lc_tablemenu.svg
new file mode 100644
index 000000000..f19ef8747
--- /dev/null
+++ b/loleaflet/images/lc_tablemenu.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg";><path d="m3 
3h18v18h-18z" fill="#fff"/><path d="m3 2c-.554 0-1 .446-1 1v18c0 .554.446 1 1 
1h18c.554 0 1-.446 1-1v-18c0-.554-.446-1-1-1zm0 1h5v6h-5zm6 0h6v6h-6zm7 
0h5v6h-5zm-13 7h5v3h-5zm6 0h6v3h-6zm7 0h5v3h-5zm-13 4h5v3h-5zm6 0h6v3h-6zm7 
0h5v3h-5zm-13 4h5v3h-5zm6 0h6v3h-6zm7 0h5v3h-5z" fill="#808080"/><path d="m2 
2h20v4h-20z" fill="#4d82b8"/></svg>
\ No newline at end of file
diff --git a/loleaflet/images/lc_view.svg b/loleaflet/images/lc_view.svg
new file mode 100644
index 000000000..69e710261
--- /dev/null
+++ b/loleaflet/images/lc_view.svg
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   viewBox="0 0 24 24"
+   version="1.1"
+   id="svg16"
+   sodipodi:docname="lc_morecontrols.svg"
+   inkscape:version="0.92.4 (unknown)">
+  <metadata
+     id="metadata22">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs20" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1017"
+     id="namedview18"
+     showgrid="false"
+     inkscape:zoom="11.313708"
+     inkscape:cx="13.521499"
+     inkscape:cy="7.2888914"
+     inkscape:window-x="0"
+     inkscape:window-y="30"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg16" />
+  <path
+     d="M 21,6 H 3 v 15 h 18"
+     id="path2"
+     inkscape:connector-curvature="0"
+     style="fill:#fcfcfc"
+     sodipodi:nodetypes="cccc" />
+  <g
+     id="g12"
+     style="fill:#808080">
+    <path
+       d="M 22,3 C 22,2.446 21.554,2 21,2 H 3 C 2.446,2 2,2.446 2,3 v 18 c 
0,0.554 0.446,1 1,1 h 18 c 0.554,0 1,-0.446 1,-1 z M 21,21 H 3 V 6 h 18 z"
+       id="path4"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cssssccccccccc" />
+    <path
+       d="m 5,8 v 4 H 9 V 8 Z m 1,1 h 2 v 2 H 6 Z"
+       id="path6"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 5,14 v 4 h 4 v -4 z m 1,1 h 2 v 2 H 6 Z"
+       id="path8"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 11,9 h 7 v 1 h -7 z"
+       id="path10"
+       inkscape:connector-curvature="0" />
+  </g>
+  <ellipse
+     cy="18.325001"
+     cx="17.704151"
+     id="circle833"
+     
style="fill:#fcfcfc;fill-opacity:1;stroke:#4d82b8;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers
 stroke fill"
+     rx="5"
+     ry="3.5" />
+  <circle
+     
style="fill:#4d82b8;fill-opacity:1;stroke:none;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers
 stroke fill"
+     id="path831"
+     cx="17.704151"
+     cy="18.325001"
+     r="2" />
+</svg>
commit 8ab2f4f01a1b0e01cbd9627238d3c89919df28f7
Author:     Pedro Pinto Silva <pedro.si...@collabora.com>
AuthorDate: Thu Mar 19 10:11:21 2020 +0100
Commit:     Andras Timar <andras.ti...@collabora.com>
CommitDate: Thu Mar 19 10:34:25 2020 +0100

    Mobile: mobilewizard: context menu: wrap; rotate; anchor: add missing icons 
and
    
    - adjust lc_wrapthrough.svg so it's visible the diference between that and 
the new icon option lc_wrapthroughtransparencytoggle.svg
    - create and add first paragraph option
    
    Change-Id: I224284e973340a15d60fa705117dd33ebb7b1450
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90734
    Tested-by: Andras Timar <andras.ti...@collabora.com>
    Reviewed-by: Andras Timar <andras.ti...@collabora.com>

diff --git a/loleaflet/images/lc_anchormenu.svg 
b/loleaflet/images/lc_anchormenu.svg
new file mode 100644
index 000000000..66bcde439
--- /dev/null
+++ b/loleaflet/images/lc_anchormenu.svg
@@ -0,0 +1,4 @@
+<svg version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg";>
+ <path d="m8.0488 2-7.0488 20h2.9863c0.32039 0 0.58719-0.09076 0.80078-0.27148 
0.2233-0.19076 0.3735-0.3963 
0.45117-0.61719l1.209-3.916h5.8047l0.044922-0.03125c0.25925-0.18599 
0.57769-0.29488 0.90625-0.29687 0.73878-0.003821 1.2821 0.55054 1.4883 
1.123v-1.6133h-0.24414c-0.87923 0-1.623-0.73808-1.623-1.6211 0-0.71181 
0.53066-1.2243 
1.1797-1.4375-0.34798-0.47875-0.55598-1.0542-0.55664-1.6719v-0.001953c-2.55e-4 
-0.90942 0.44146-1.7171 1.1113-2.2441l-2.6074-7.4004h-3.9023zm1.9375 
3.6445c0.12621 0.49197 0.25074 0.94941 0.37695 1.3711 0.12621 0.41165 0.24873 
0.77274 0.36524 1.084l1.9648 6.3242h-5.3867l1.9648-6.3086c0.1068-0.31124 
0.2234-0.67792 0.34961-1.0996 0.12621-0.42169 0.24873-0.87913 0.36523-1.3711z" 
fill="#696969"/>
+ <path d="m16.314 9.7778a1.8667 1.8667 0 0 0-1.8667 1.8667 1.8667 1.8667 0 0 0 
1.2444 1.7585v0.73036h-1.2444c-0.34471 0-0.62222 0.27751-0.62222 
0.62222s0.27751 0.62222 0.62222 
0.62222h1.2444v5.5563c-1.0596-0.13504-2.0494-0.60481-2.8207-1.3466l0.66111-0.46302a0.61243
 0.67872 0 0 0-0.32326-1.2578 0.61243 0.67872 0 0 0-0.33056 0.11059l-2.4876 
1.7379a0.61265 0.67896 0 1 0 0.65383 1.1484l0.79476-0.55538c1.1666 1.213 2.7771 
1.9128 4.4746 1.9141 1.698-9.33e-4 3.309-0.70087 4.4759-1.9141l0.79358 
0.55538a0.61265 0.67896 0 1 0 0.65383-1.1484l-2.4876-1.7379a0.61243 0.67872 0 0 
0-0.33056-0.11059 0.61243 0.67872 0 0 0-0.32326 1.2578l0.66478 0.46545c-0.7723 
0.74169-1.7638 1.2114-2.8243 1.3453v-5.5574h1.2444c0.34471 0 0.62222-0.27751 
0.62222-0.62222 
0-0.34471-0.27751-0.62222-0.62222-0.62222h-1.2444v-0.73161a1.8667 1.8667 0 0 0 
1.2444-1.7573 1.8667 1.8667 0 0 0-1.8667-1.8667zm0 1.2444a0.62222 0.62222 0 0 1 
0.62222 0.62222 0.62222 0.62222 0 0 1-0.62222 0.62222 0.62222 0.62222 0 0 
1-0.62222-0.62222 0
 .62222 0.62222 0 0 1 0.62222-0.62222z" fill="#4d82b8" stroke-width=".62222"/>
+</svg>
diff --git a/loleaflet/images/lc_rotateleft.svg 
b/loleaflet/images/lc_rotateleft.svg
new file mode 100644
index 000000000..1f33cc671
--- /dev/null
+++ b/loleaflet/images/lc_rotateleft.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg";><path d="m1.499971 
21.5h7.000029v-15z" fill="#4d82b8" fill-rule="evenodd" stroke="#4d82b8" 
stroke-linecap="round" stroke-linejoin="round"/><path d="m10.5 
14.5v7.000008h12z" fill="#fff" fill-rule="evenodd" stroke="#808080" 
stroke-linecap="round" stroke-linejoin="round"/><g transform="matrix(0 -1 -1 0 
30.992858 27)"><path d="m21.5 16.5-2.995 2.992879-3.005-2.992879" fill="none" 
stroke="#4d82b8" stroke-linecap="round" stroke-linejoin="round"/><path d="m13.5 
11.007943c-.276142 0-.5.223858-.5.5s.223858.5.5.5h2c1.380712 0 2.5 1.119287 2.5 
2.5h.0059v.423829 
4.068228h1v-4.068228-.5c-.0059-2.154663-1.88341-3.373799-3.5059-3.423829z" 
fill="#4d82b8" fill-rule="evenodd"/></g></svg>
\ No newline at end of file
diff --git a/loleaflet/images/lc_rotateright.svg 
b/loleaflet/images/lc_rotateright.svg
new file mode 100644
index 000000000..40e86d9b3
--- /dev/null
+++ b/loleaflet/images/lc_rotateright.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg";><g 
transform="matrix(-1 0 0 1 23.999864 0)"><path d="m1.499971 
21.5h7.000029v-15.0000002z" fill="#4d82b8" fill-rule="evenodd" stroke="#4d82b8" 
stroke-linecap="round" stroke-linejoin="round"/><path d="m10.5 
14.5v7.000008h11.999999z" fill="#fff" fill-rule="evenodd" stroke="#808080" 
stroke-linecap="round" stroke-linejoin="round"/><g transform="matrix(0 -1 -1 0 
30.992857 27)"><path d="m21.5 16.5-2.995 2.992879-3.005-2.992879" fill="none" 
stroke="#4d82b8" stroke-linecap="round" stroke-linejoin="round"/><path d="m13.5 
11.007943c-.276142 0-.5.223858-.5.5s.223858.5.5.5h2c1.380712 0 2.5 1.119287 2.5 
2.5h.0059v.423829 
4.068228h1v-4.068228-.5c-.0059-2.154663-1.88341-3.373799-3.5059-3.423829z" 
fill="#4d82b8" fill-rule="evenodd"/></g></g></svg>
\ No newline at end of file
diff --git a/loleaflet/images/lc_wrapanchoronly.svg 
b/loleaflet/images/lc_wrapanchoronly.svg
new file mode 100644
index 000000000..dcd117fde
--- /dev/null
+++ b/loleaflet/images/lc_wrapanchoronly.svg
@@ -0,0 +1,8 @@
+<svg version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg";>
+ <rect x="14.153" y="3" width="7.8474" height="1" ry=".44607" fill="#4d82b8" 
stroke-width=".62639"/>
+ <g fill="#808080">
+  <rect x="2" y="21" width="20" height="1" ry=".44607"/>
+  <rect x="2" y="18" width="20" height="1" ry=".44607"/>
+ </g>
+ <path d="m6.0201 3c-0.025732 0-0.049852 0.00598-0.075458 0.00653a3.9823 
3.9823 0 0 0-0.59847 0.052052c-0.00473 7.997e-4 -0.00956 0.00177-0.014308 
0.00255a3.9823 3.9823 0 0 0-3.3319 3.9265 3.9823 3.9823 0 0 0 1.906 
3.3917c0.047967 0.03013 0.095101 0.06035 0.14441 0.08847a3.9823 3.9823 0 0 0 
0.42153 0.20166c0.10916 0.04623 0.22048 0.08577 0.33436 0.1223 0.38422 0.12322 
0.78845 0.20166 1.2138 
0.20166v5.329h1.3322v-11.99h1.3322v11.99h1.3322v-11.99l2.6645-0.66612v-0.66612z"
 fill="#4d82b8" stroke-width=".66612"/>
+</svg>
diff --git a/loleaflet/images/lc_wrapcontour.svg 
b/loleaflet/images/lc_wrapcontour.svg
new file mode 100644
index 000000000..8a6981c36
--- /dev/null
+++ b/loleaflet/images/lc_wrapcontour.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg";><rect 
fill="#808080" height="1" ry=".446068" transform="scale(-1 1)" width="20" 
x="-22" y="3"/><rect fill="#4d82b8" height="8.919747" ry=".743312" 
transform="matrix(.70710679 .70710677 -.70710679 .70710677 0 0)" 
width="8.919747" x="16.75333" y="-5.874086"/><g fill="#808080" 
transform="scale(-1 1)"><rect height="1" ry=".446068" width="20" x="-22" 
y="6"/><rect height="1" ry=".446068" width="20" x="-22" y="21"/><rect 
height="1" ry=".410114" width="5.999999" x="-8" y="15"/><rect height="1" 
ry=".423729" width="6.000001" x="-8" y="12"/><rect height="1" ry=".419102" 
width="9" x="-11" y="9"/><rect height="1" ry=".410114" width="9" x="-11" 
y="18"/></g></svg>
\ No newline at end of file
diff --git a/loleaflet/images/lc_wrapthrough.svg 
b/loleaflet/images/lc_wrapthrough.svg
index f6010d01e..b34b507e0 100644
--- a/loleaflet/images/lc_wrapthrough.svg
+++ b/loleaflet/images/lc_wrapthrough.svg
@@ -1 +1,12 @@
-<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg";><rect 
fill="#808080" height="1" ry=".446068" width="20" x="2" y="3"/><rect 
fill="#4d82b8" height="9" ry="1" width="10" x="7" y="8"/><g 
fill="#808080"><rect height="1" ry=".446068" width="20" x="2" y="6"/><rect 
height="1" ry=".446068" width="20" x="2" y="21"/><rect height="1" ry=".446068" 
width="20" x="2" y="12"/><rect height="1" ry=".446068" width="20" x="2" 
y="18"/><rect height="1" ry=".446068" width="20" x="2" y="9"/><rect height="1" 
ry=".446068" width="20" x="2" y="15"/></g></svg>
\ No newline at end of file
+<svg version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg";>
+ <rect x="2" y="3" width="20" height="1" ry=".44607" fill="#808080"/>
+ <g fill="#808080">
+  <rect x="2" y="6" width="20" height="1" ry=".44607"/>
+  <rect x="2" y="21" width="20" height="1" ry=".44607"/>
+  <rect x="2" y="12" width="20" height="1" ry=".44607"/>
+  <rect x="2" y="18" width="20" height="1" ry=".44607"/>
+  <rect x="2" y="9" width="20" height="1" ry=".44607"/>
+  <rect x="2" y="15" width="20" height="1" ry=".44607"/>
+ </g>
+ <rect x="7" y="8" width="10" height="9" ry="1" fill="#4d82b8"/>
+</svg>
diff --git a/loleaflet/images/lc_wrapthroughtransparencytoggle.svg 
b/loleaflet/images/lc_wrapthroughtransparencytoggle.svg
new file mode 100644
index 000000000..37f749f89
--- /dev/null
+++ b/loleaflet/images/lc_wrapthroughtransparencytoggle.svg
@@ -0,0 +1,12 @@
+<svg version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg";>
+ <rect x="7" y="8" width="10" height="9" ry="1" fill="#4d82b8"/>
+ <rect x="2" y="3" width="20" height="1" ry=".44607" fill="#808080"/>
+ <g fill="#808080">
+  <rect x="2" y="6" width="20" height="1" ry=".44607"/>
+  <rect x="2" y="21" width="20" height="1" ry=".44607"/>
+  <rect x="2" y="12" width="20" height="1" ry=".44607"/>
+  <rect x="2" y="18" width="20" height="1" ry=".44607"/>
+  <rect x="2" y="9" width="20" height="1" ry=".44607"/>
+  <rect x="2" y="15" width="20" height="1" ry=".44607"/>
+ </g>
+</svg>
commit 583547926e5f47cbcbc96ade04df4d5766f46692
Author:     Jan Holesovsky <ke...@collabora.com>
AuthorDate: Thu Mar 19 08:58:46 2020 +0100
Commit:     Andras Timar <andras.ti...@collabora.com>
CommitDate: Thu Mar 19 09:03:55 2020 +0100

    android: Breaking the build on extra translation is a nonsense.
    
    The next translation update will kill the extra string anyway.
    
    Change-Id: Ieda2ea51837e12fe40d2c85ff2d99afc65a2989b
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90731
    Tested-by: Andras Timar <andras.ti...@collabora.com>
    Reviewed-by: Andras Timar <andras.ti...@collabora.com>

diff --git a/android/app/build.gradle b/android/app/build.gradle
index 6ca998f58..0ac10b9f6 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -13,7 +13,7 @@ android {
     }
 
     lintOptions {
-        disable 'MissingTranslation'
+        disable 'MissingTranslation', 'ExtraTranslation'
     }
 
     buildTypes {
diff --git a/android/lib/build.gradle b/android/lib/build.gradle
index 812a8a7aa..0fa72b561 100644
--- a/android/lib/build.gradle
+++ b/android/lib/build.gradle
@@ -19,7 +19,7 @@ android {
     }
 
     lintOptions {
-        disable 'MissingTranslation'
+        disable 'MissingTranslation', 'ExtraTranslation'
     }
 
     buildTypes {
commit 8f86c5929568fcbba9cc36c428744cbc58dc2dd4
Author:     Weblate <nore...@documentfoundation.org>
AuthorDate: Wed Mar 18 22:35:26 2020 +0100
Commit:     Andras Timar <andras.ti...@collabora.com>
CommitDate: Thu Mar 19 09:01:45 2020 +0100

    update translations
    
    LibreOffice Online/android-app (Ukrainian)
    Currently translated at 100.0% (100 of 100 strings)
    
    Change-Id: I1e0750a6d3fecb7736cc2df4c1e4b600187fd858
    
    update translations
    
    LibreOffice Online/android-lib (Czech)
    Currently translated at 100.0% (12 of 12 strings)
    
    Change-Id: I35f5302c62e6950459b2fdb0b169f9445f8b8034
    
    update translations
    
    LibreOffice Online/android-lib (Ukrainian)
    Currently translated at 100.0% (12 of 12 strings)
    
    Change-Id: Ide65bc2675a820111f150b13519ae2c1ec8b57c2
    
    update translations
    
    LibreOffice Online/android-lib (Polish)
    Currently translated at 100.0% (12 of 12 strings)
    
    Change-Id: I2917662b4663d3e13f5564e822f9f3652c1bcc7a
    
    update translations
    
    LibreOffice Online/android-app (Hungarian)
    Currently translated at 100.0% (100 of 100 strings)
    
    Change-Id: I82425b31fff35f1a00767d1497dc0123af9d4321
    
    update translations
    
    LibreOffice Online/android-lib (Hungarian)
    Currently translated at 100.0% (12 of 12 strings)
    
    Change-Id: I4ca0eb2624bbb7c90742bd42811c8c9be301e423
    
    update translations
    
    LibreOffice Online/android-lib (Portuguese (Brazil))
    Currently translated at 100.0% (12 of 12 strings)
    
    Change-Id: Ia25abe8feca105d849ca35a4f8e02e7b0aa8fab1
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90714
    Tested-by: Andras Timar <andras.ti...@collabora.com>
    Reviewed-by: Andras Timar <andras.ti...@collabora.com>

diff --git a/android/app/src/main/res/values-hu/strings.xml 
b/android/app/src/main/res/values-hu/strings.xml
index d18f6bd2b..4cac2534b 100644
--- a/android/app/src/main/res/values-hu/strings.xml
+++ b/android/app/src/main/res/values-hu/strings.xml
@@ -99,4 +99,5 @@
     <string name="share_document">Dokumentum megosztása</string>
     <string name="pref_enable_chrome_debugger_info">A Chrome hibakereső 
eszközének engedélyezése a dokumentumban</string>
     <string name="pref_enable_chrome_debugger">Hibakeresés Chrome-mal</string>
+    <string name="pref_category_editor">A szerkesztő beállításai</string>
 </resources>
\ No newline at end of file
diff --git a/android/app/src/main/res/values-uk/strings.xml 
b/android/app/src/main/res/values-uk/strings.xml
index f10ad51f2..a18ec71bd 100644
--- a/android/app/src/main/res/values-uk/strings.xml
+++ b/android/app/src/main/res/values-uk/strings.xml
@@ -99,4 +99,5 @@
     <string name="server_url_and_port">URL і порт хмарного сервера.</string>
     <string name="server_url">URL сервера</string>
     <string name="otg_warning">Експериментальна можливість: Використовувати, 
тільки якщо OTG пристрій доступний для запису.</string>
+    <string name="pref_category_editor">Налаштування Редактора</string>
 </resources>
\ No newline at end of file
diff --git a/android/lib/src/main/res/values-cs/strings.xml 
b/android/lib/src/main/res/values-cs/strings.xml
index 4bb7bf8eb..72170a53e 100644
--- a/android/lib/src/main/res/values-cs/strings.xml
+++ b/android/lib/src/main/res/values-cs/strings.xml
@@ -10,4 +10,7 @@
     <string name="saving">Ukládání...</string>
     <string name="rate_our_app_title">Děkujeme, že používáte %1$s!</string>
     <string name="later">Později</string>
+    <string name="exiting">Ukončování...</string>
+    <string name="rate_our_app_text">Pokud se vám tato aplikace líbí, dejte jí 
na Google Play 5 hvězdiček. Příznivé recenze jsou pro nás tou nejlepší 
motivací.</string>
+    <string name="rate_now">Ohodnotit nyní</string>
 </resources>
\ No newline at end of file
diff --git a/android/lib/src/main/res/values-hu/strings.xml 
b/android/lib/src/main/res/values-hu/strings.xml
index 8749b1a2f..bb910d83e 100644
--- a/android/lib/src/main/res/values-hu/strings.xml
+++ b/android/lib/src/main/res/values-hu/strings.xml
@@ -8,4 +8,9 @@
     <string name="temp_file_saving_disabled">Ez a fájl írásvédett, a mentés le 
van tiltva.</string>
     <string name="preparing_for_the_first_start_after_an_update">Előkészülés a 
frissítés utáni első indításhoz.</string>
     <string name="saving">Mentés...</string>
+    <string name="exiting">Kilépés...</string>
+    <string name="rate_our_app_text">Ha tetszik az alkalmazás, kérjük, adjon 5 
csillagot a Google Play-en. A pozitív visszajelzések motiválnak minket a 
legjobban.</string>
+    <string name="rate_our_app_title">Köszönjük, hogy a %1$s-t 
használja!</string>
+    <string name="later">Később</string>
+    <string name="rate_now">Értékelés most</string>
 </resources>
\ No newline at end of file
diff --git a/android/lib/src/main/res/values-pl/strings.xml 
b/android/lib/src/main/res/values-pl/strings.xml
index 7bdb9e4ed..952a016f0 100644
--- a/android/lib/src/main/res/values-pl/strings.xml
+++ b/android/lib/src/main/res/values-pl/strings.xml
@@ -8,4 +8,9 @@
     <string name="failed_to_load_file">Nie udało się określić pliku do 
załadowania</string>
     <string name="storage_permission_required">Wymagane jest pozwolenie na 
przechowywanie</string>
     <string name="temp_file_saving_disabled">Ten plik jest tylko do odczytu, 
zapisywanie jest wyłączone.</string>
+    <string name="exiting">Wychodzenie...</string>
+    <string name="rate_our_app_text">Jeśli Ci się spodoba, daj nam 5 gwiazdek 
w Google Play. Twoja pozytywna recenzja jest naszą największą 
motywacją.</string>
+    <string name="rate_our_app_title">Dziękujemy za używanie %1$s!</string>
+    <string name="later">Później</string>
+    <string name="rate_now">Oceń teraz</string>
 </resources>
\ No newline at end of file
diff --git a/android/lib/src/main/res/values-pt-rBR/strings.xml 
b/android/lib/src/main/res/values-pt-rBR/strings.xml
index c93ace1da..98b088211 100644
--- a/android/lib/src/main/res/values-pt-rBR/strings.xml
+++ b/android/lib/src/main/res/values-pt-rBR/strings.xml
@@ -13,4 +13,5 @@
     <string name="rate_our_app_text">Se gostou dele, marque 5 estrelas no 
Google Play. Sua avaliação positiva é nossa melhor motivação.</string>
     <string name="rate_our_app_title">Obrigado por usar %1$s!</string>
     <string name="later">Mais tarde</string>
+    <string name="exiting">Saindo...</string>
 </resources>
\ No newline at end of file
diff --git a/android/lib/src/main/res/values-uk/strings.xml 
b/android/lib/src/main/res/values-uk/strings.xml
index 6d8e030ba..66c7a3d4b 100644
--- a/android/lib/src/main/res/values-uk/strings.xml
+++ b/android/lib/src/main/res/values-uk/strings.xml
@@ -8,4 +8,9 @@
     <string name="failed_to_load_file">Помилка визначення файлу для 
завантаження</string>
     <string name="storage_permission_required">Необхідний дозвіл на 
збереження</string>
     <string name="temp_file_saving_disabled">Цей файл тільки для читання, 
збереження неможливе.</string>
+    <string name="exiting">Виходимо...</string>
+    <string name="rate_our_app_text">Якщо Вам це подобається, будь ласка, 
поставте нам 5 зірочок в Google Play. Ваші гарні відгуки - наша найкраща 
мотивація.</string>
+    <string name="rate_our_app_title">Дякуємо за використання %1$s!</string>
+    <string name="later">Пізніше</string>
+    <string name="rate_now">Оцінити зараз</string>
 </resources>
\ No newline at end of file
commit a09a0877d17ccee383403c4c4d6bd4427e6fcdde
Author:     Henry Castro <hcas...@collabora.com>
AuthorDate: Thu Mar 12 23:29:39 2020 -0400
Commit:     Andras Timar <andras.ti...@collabora.com>
CommitDate: Thu Mar 19 08:56:09 2020 +0100

    loleaflet: ensure forward message if early websocket is connected
    
    This should never happen, since the _onMessage is re-assigned when
    loadDocument is called, but it is better to ensure to forward all
    messages.
    
    Change-Id: I9a792bc077b26f2f92c30c4e7851c9d2b2637bfb
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90449
    Tested-by: Andras Timar <andras.ti...@collabora.com>
    Reviewed-by: Andras Timar <andras.ti...@collabora.com>

diff --git a/loleaflet/js/global.js b/loleaflet/js/global.js
index f98450fb0..a08c4cf3b 100644
--- a/loleaflet/js/global.js
+++ b/loleaflet/js/global.js
@@ -320,6 +320,7 @@
 
                global.socket.onmessage = function (event) {
                        if (typeof global.socket._onMessage === 'function') {
+                               global.socket._emptyQueue();
                                global.socket._onMessage(event);
                        } else {
                                global.queueMsg.push(event.data);
diff --git a/loleaflet/src/core/Socket.js b/loleaflet/src/core/Socket.js
index a173ef784..b4118479f 100644
--- a/loleaflet/src/core/Socket.js
+++ b/loleaflet/src/core/Socket.js
@@ -76,8 +76,11 @@ L.Socket = L.Class.extend({
                }
 
                // process messages for early socket connection
-               if (socket && ((socket.readyState === 1 || socket.readyState 
=== 0)) &&
-                       window.queueMsg && window.queueMsg.length > 0) {
+               this._emptyQueue();
+       },
+
+       _emptyQueue: function () {
+               if (window.queueMsg && window.queueMsg.length > 0) {
                        for (var it = 0; it < window.queueMsg.length; it++) {
                                this._onMessage({data: window.queueMsg[it]});
                        }
commit 1c4a66e736d73ab372745972a3bfcbb611e2de8a
Author:     Henry Castro <hcas...@collabora.com>
AuthorDate: Thu Mar 12 23:43:15 2020 -0400
Commit:     Andras Timar <andras.ti...@collabora.com>
CommitDate: Wed Mar 18 23:20:27 2020 +0100

    lolealet: fill the font list when data is received
    
    The changes only populate the font list when the data
    is received by the client, the other approach iterates
    all toolbar items to refresh each one.
    
    Change-Id: I837b52275b49e025fa353dcf088f97c19779bc79
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/90450
    Tested-by: Andras Timar <andras.ti...@collabora.com>
    Reviewed-by: Andras Timar <andras.ti...@collabora.com>

diff --git a/loleaflet/src/control/Control.Toolbar.js 
b/loleaflet/src/control/Control.Toolbar.js
index ea80836a4..d6e8ce67d 100644
--- a/loleaflet/src/control/Control.Toolbar.js
+++ b/loleaflet/src/control/Control.Toolbar.js
@@ -2090,6 +2090,39 @@ function onCommandValues(e) {
        }
 }
 
+function updateToolbarCommandValues(e) {
+       if (e.commandName === '.uno:CharFontName') {
+               // 2) For .uno:CharFontName
+               var commandValues = map.getToolbarCommandValues(e.commandName);
+               if (typeof commandValues === 'undefined') {
+                       return;
+               }
+
+               var data = []; // reset data in order to avoid that the font 
select box is populated with styles, too.
+               // Old browsers like IE11 et al don't like Object.keys with
+               // empty arguments
+               if (typeof commandValues === 'object') {
+                       data = data.concat(Object.keys(commandValues));
+               }
+
+               /* debug messages it will be removed later */
+               if (data.length < 3) {
+                       console.log('ALERT!, the server is sending a small font 
list');
+               }
+               /* debug end*/
+
+               $('.fonts-select').select2({
+                       data: data.sort(function (a, b) {  // also 
sort(localely)
+                               return a.localeCompare(b);
+                       }),
+                       placeholder: _('Font')
+               });
+               $('.fonts-select').on('select2:select', onFontSelect);
+               $('.fonts-select').val(fontsSelectValue).trigger('change');
+               w2ui['editbar'].resize();
+       }
+}
+
 function updateCommandValues(targetName) {
        var data = [];
        // 1) For .uno:StyleApply
@@ -2162,29 +2195,6 @@ function updateCommandValues(targetName) {
                $('.styles-select').on('select2:select', onStyleSelect);
                w2ui['editbar'].resize();
        }
-
-       if (targetName === 'fonts' && $('.fonts-select option').length === 1) {
-               // 2) For .uno:CharFontName
-               commandValues = 
map.getToolbarCommandValues('.uno:CharFontName');
-               if (typeof commandValues === 'undefined') {
-                       return;
-               }
-               data = []; // reset data in order to avoid that the font select 
box is populated with styles, too.
-               // Old browsers like IE11 et al don't like Object.keys with
-               // empty arguments
-               if (typeof commandValues === 'object') {
-                       data = data.concat(Object.keys(commandValues));
-               }
-               $('.fonts-select').select2({
-                       data: data.sort(function (a, b) {  // also 
sort(localely)
-                               return a.localeCompare(b);
-                       }),
-                       placeholder: _('Font')

... etc. - the rest is truncated
_______________________________________________
Libreoffice-commits mailing list
libreoffice-comm...@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to