common/Common.hpp | 2 debian/changelog | 6 kit/Kit.cpp | 4 loleaflet/build/deps.js | 7 loleaflet/dist/leaflet.css | 8 loleaflet/dist/loleaflet.css | 15 + loleaflet/dist/menubar.css | 22 ++ loleaflet/src/control/Control.Menubar.js | 1 loleaflet/src/control/Control.MobileInput.js | 195 ++++++++++++++++++++++ loleaflet/src/core/Socket.js | 3 loleaflet/src/layer/marker/ClipboardContainer.js | 4 loleaflet/src/layer/tile/GridLayer.js | 13 + loleaflet/src/layer/tile/TileLayer.js | 25 +- loleaflet/src/map/Map.js | 19 +- loolwsd.spec.in | 2 loolwsd.xml.in | 1 net/Socket.cpp | 4 test/TileCacheTests.cpp | 198 +++++++++++++++++++++++ wsd/Admin.cpp | 4 wsd/Auth.cpp | 2 wsd/Auth.hpp | 5 wsd/ClientSession.cpp | 66 ++++++- wsd/ClientSession.hpp | 25 -- wsd/DocumentBroker.cpp | 67 +++++-- wsd/FileServer.cpp | 9 - wsd/LOOLWSD.cpp | 16 + wsd/TestStubs.cpp | 2 wsd/TileCache.cpp | 48 ++++- wsd/TileCache.hpp | 8 29 files changed, 685 insertions(+), 96 deletions(-)
New commits: commit fa40d73f1466d34a9562d9a1a49f99c3cdbdc9f2 Author: Andras Timar <andras.ti...@collabora.com> AuthorDate: Fri Oct 5 20:39:00 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:39:00 2018 +0200 Bump package version to 3.4.0-5 Change-Id: I6ea9ddf09e0eaa6a7a088e679a07193df818b8ef diff --git a/debian/changelog b/debian/changelog index 8bbcd69f1..32f7f0044 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +loolwsd (3.4.0-5) unstable; urgency=medium + + * see the git log: http://col.la/coolcd34 + + -- Andras Timar <andras.ti...@collabora.com> Fri, 05 Oct 2018 20:30:00 +0200 + loolwsd (3.4.0-4) unstable; urgency=medium * see the git log: http://col.la/coolcd34 diff --git a/loolwsd.spec.in b/loolwsd.spec.in index bf9dee572..c54d376a7 100644 --- a/loolwsd.spec.in +++ b/loolwsd.spec.in @@ -12,7 +12,7 @@ Name: loolwsd%{name_suffix} Name: loolwsd %endif Version: @PACKAGE_VERSION@ -Release: 4%{?dist} +Release: 5%{?dist} %if 0%{?suse_version} == 1110 Group: Productivity/Office/Suite BuildRoot: %{_tmppath}/%{name}-%{version}-build commit 587bc7bdb4b541ec4676974cf53066a61937cca3 Author: Andras Timar <andras.ti...@collabora.com> AuthorDate: Mon Oct 1 20:17:35 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:37:18 2018 +0200 tdf#115163 allow bind to loopback interface Change-Id: I4808fb0fd685dfe990efd5fb739ee86f1276ffad Reviewed-on: https://gerrit.libreoffice.org/61412 Reviewed-by: Aron Budea <aron.bu...@collabora.com> Tested-by: Aron Budea <aron.bu...@collabora.com> (cherry picked from commit 1d087b3545be712073ab52ed11352a6b686f7a63) diff --git a/loolwsd.xml.in b/loolwsd.xml.in index 587452ba4..26e5db7a8 100644 --- a/loolwsd.xml.in +++ b/loolwsd.xml.in @@ -68,6 +68,7 @@ <net desc="Network settings"> <proto type="string" default="all" desc="Protocol to use IPv4, IPv6 or all for both">all</proto> + <listen type="string" default="any" desc="Listen address that loolwsd binds to. Can be 'any' or 'loopback'.">any</listen> <service_root type="path" default="" desc="Prefix all the pages, websockets, etc. with this path."></service_root> <post_allow desc="Allow/deny client IP address for POST(REST)." allow="true"> <host desc="The IPv4 private 192.168 block as plain IPv4 dotted decimal addresses.">192\.168\.[0-9]{1,3}\.[0-9]{1,3}</host> diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp index eca0644f6..b55ec5617 100644 --- a/wsd/LOOLWSD.cpp +++ b/wsd/LOOLWSD.cpp @@ -168,6 +168,9 @@ int ClientPortNumber = DEFAULT_CLIENT_PORT_NUMBER; /// Protocols to listen on Socket::Type ClientPortProto = Socket::Type::All; +/// INET address to listen on +ServerSocket::Type ClientListenAddr = ServerSocket::Type::Public; + /// Port for prisoners to connect to int MasterPortNumber = DEFAULT_MASTER_PORT_NUMBER; @@ -698,6 +701,7 @@ void LOOLWSD::initialize(Application& self) { "loleaflet_html", "loleaflet.html" }, { "loleaflet_logging", "false" }, { "net.proto", "all" }, + { "net.listen", "any" }, { "net.service_root", "" }, { "num_prespawn_children", "1" }, { "per_document.autosave_duration_secs", "300" }, @@ -874,6 +878,16 @@ void LOOLWSD::initialize(Application& self) LOG_WRN("Invalid protocol: " << proto); } + { + std::string listen = getConfigValue<std::string>(conf, "net.listen", ""); + if (!Poco::icompare(listen, "any")) + ClientListenAddr = ServerSocket::Type::Public; + else if (!Poco::icompare(listen, "loopback")) + ClientListenAddr = ServerSocket::Type::Local; + else + LOG_WRN("Invalid listen address: " << listen << ". Falling back to default: 'any'" ); + } + // Prefix for the loolwsd pages; should not end with a '/' ServiceRoot = getPathFromConfig("net.service_root"); while (ServiceRoot.length() > 0 && ServiceRoot[ServiceRoot.length() - 1] == '/') @@ -2734,7 +2748,7 @@ private: factory = std::make_shared<PlainSocketFactory>(); std::shared_ptr<ServerSocket> socket = getServerSocket( - ServerSocket::Type::Public, port, WebServerPoll, factory); + ClientListenAddr, port, WebServerPoll, factory); while (!socket) { ++port; commit 30e5f4fff318803457da8ca8d053a48d8118cba6 Author: Andras Timar <andras.ti...@collabora.com> AuthorDate: Fri Sep 28 11:54:20 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:37:12 2018 +0200 don't use ssl key file for admin console auth, use a generated key instead Change-Id: I424afe0184a64b7f069d896bde6941e42b7b5531 rational: setup is easier in case, when user does not use ssl in loolwsd config Reviewed-on: https://gerrit.libreoffice.org/61411 Reviewed-by: Aron Budea <aron.bu...@collabora.com> Tested-by: Aron Budea <aron.bu...@collabora.com> (cherry picked from commit 86f50208829772934ce310be103ec9a36c862d7f) diff --git a/wsd/Admin.cpp b/wsd/Admin.cpp index 9c24d2007..f93cf75b7 100644 --- a/wsd/Admin.cpp +++ b/wsd/Admin.cpp @@ -76,11 +76,9 @@ void AdminSocketHandler::handleMessage(bool /* fin */, WSOpCode /* code */, } std::string jwtToken; LOOLProtocol::getTokenString(tokens[1], "jwt", jwtToken); - const auto& config = Application::instance().config(); - const auto sslKeyPath = config.getString("ssl.key_file_path", ""); LOG_INF("Verifying JWT token: " << jwtToken); - JWTAuth authAgent(sslKeyPath, "admin", "admin", "admin"); + JWTAuth authAgent("admin", "admin", "admin"); if (authAgent.verify(jwtToken)) { LOG_TRC("JWT token is valid"); diff --git a/wsd/Auth.cpp b/wsd/Auth.cpp index 088719d78..b1f76ae60 100644 --- a/wsd/Auth.cpp +++ b/wsd/Auth.cpp @@ -37,6 +37,8 @@ using Poco::Base64Decoder; using Poco::Base64Encoder; using Poco::OutputLineEndingConverter; +const Poco::Crypto::RSAKey JWTAuth::_key(Poco::Crypto::RSAKey(Poco::Crypto::RSAKey::KL_2048, Poco::Crypto::RSAKey::EXP_LARGE)); + void Authorization::authorizeURI(Poco::URI& uri) const { if (_type == Authorization::Type::Token) diff --git a/wsd/Auth.hpp b/wsd/Auth.hpp index 96bcb86b6..fa9029bba 100644 --- a/wsd/Auth.hpp +++ b/wsd/Auth.hpp @@ -69,11 +69,10 @@ public: class JWTAuth : public AuthBase { public: - JWTAuth(const std::string& keyPath, const std::string& name, const std::string& sub, const std::string& aud) + JWTAuth(const std::string& name, const std::string& sub, const std::string& aud) : _name(name), _sub(sub), _aud(aud), - _key(Poco::Crypto::RSAKey("", keyPath)), _digestEngine(_key, "SHA256") { } @@ -96,7 +95,7 @@ private: const std::string _sub; const std::string _aud; - const Poco::Crypto::RSAKey _key; + static const Poco::Crypto::RSAKey _key; Poco::Crypto::RSADigestEngine _digestEngine; }; diff --git a/wsd/FileServer.cpp b/wsd/FileServer.cpp index 35b43c27e..714dac28b 100644 --- a/wsd/FileServer.cpp +++ b/wsd/FileServer.cpp @@ -196,7 +196,6 @@ bool FileServerRequestHandler::isAdminLoggedIn(const HTTPRequest& request, assert(LOOLWSD::AdminEnabled); const auto& config = Application::instance().config(); - const auto sslKeyPath = config.getString("ssl.key_file_path", ""); NameValueCollection cookies; request.getCookies(cookies); @@ -204,7 +203,7 @@ bool FileServerRequestHandler::isAdminLoggedIn(const HTTPRequest& request, { const std::string jwtToken = cookies.get("jwt"); LOG_INF("Verifying JWT token: " << jwtToken); - JWTAuth authAgent(sslKeyPath, "admin", "admin", "admin"); + JWTAuth authAgent("admin", "admin", "admin"); if (authAgent.verify(jwtToken)) { LOG_TRC("JWT token is valid"); @@ -247,7 +246,7 @@ bool FileServerRequestHandler::isAdminLoggedIn(const HTTPRequest& request, } // authentication passed, generate and set the cookie - JWTAuth authAgent(sslKeyPath, "admin", "admin", "admin"); + JWTAuth authAgent("admin", "admin", "admin"); const std::string jwtToken = authAgent.getAccessToken(); Poco::Net::HTTPCookie cookie("jwt", jwtToken); commit 70ca7e3bc3f7ab4652613c99b7778182c493ad4b Author: Andras Timar <andras.ti...@collabora.com> AuthorDate: Wed Oct 3 13:25:36 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:37:07 2018 +0200 fix that internal port 9981 was opened on all interfaces Change-Id: I04cd12b7fa2f0be9b08a3d325f08b36ca2ce240e Reviewed-on: https://gerrit.libreoffice.org/61410 Reviewed-by: Aron Budea <aron.bu...@collabora.com> Tested-by: Aron Budea <aron.bu...@collabora.com> (cherry picked from commit f32b75eefe5ac2b4ac5b54039e3b4bb665b994d6) diff --git a/net/Socket.cpp b/net/Socket.cpp index b78900276..35b713632 100644 --- a/net/Socket.cpp +++ b/net/Socket.cpp @@ -312,9 +312,9 @@ bool ServerSocket::bind(Type type, int port) addrv4.sin_family = AF_INET; addrv4.sin_port = htons(port); if (type == Type::Public) - addrv4.sin_addr.s_addr = type == htonl(INADDR_ANY); + addrv4.sin_addr.s_addr = htonl(INADDR_ANY); else - addrv4.sin_addr.s_addr = type == htonl(INADDR_LOOPBACK); + addrv4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); rc = ::bind(getFD(), (const sockaddr *)&addrv4, sizeof(addrv4)); } commit ca2d2b4a33c9b54f9644944373fcc295a4f83cfd Author: Henry Castro <hcas...@collabora.com> AuthorDate: Wed Oct 3 21:49:31 2018 -0400 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:37:03 2018 +0200 loleaflet: mobile: use max-device-height media query max-height it is used for window size, max-device-height it is the device screen dimensions, so when a tablet rotate to landscape we keep the mobile layout Change-Id: I921007014a63374114ec7563144f3532a53fd021 Reviewed-on: https://gerrit.libreoffice.org/61339 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> (cherry picked from commit 63491fe0f11749f04cddd0599f7b99253c7d0479) diff --git a/loleaflet/dist/leaflet.css b/loleaflet/dist/leaflet.css index 332490ed5..3dde4a01a 100644 --- a/loleaflet/dist/leaflet.css +++ b/loleaflet/dist/leaflet.css @@ -788,7 +788,7 @@ input.clipboard { } } -@media (max-width: 768px),(max-height: 768px) { +@media (max-width: 768px),(max-device-height: 768px) { .loleaflet-ruler { height: 0px; display: none; diff --git a/loleaflet/dist/loleaflet.css b/loleaflet/dist/loleaflet.css index e7d38b5eb..3ce760548 100644 --- a/loleaflet/dist/loleaflet.css +++ b/loleaflet/dist/loleaflet.css @@ -81,7 +81,7 @@ body { width: 0; } -@media (max-width: 768px),(max-height: 768px) { +@media (max-width: 768px), (max-device-height: 768px) { /* Show slidesorter beyond 768px only */ #presentation-controls-wrapper { display: none; diff --git a/loleaflet/dist/menubar.css b/loleaflet/dist/menubar.css index 7af9d31b6..f248ec4e0 100644 --- a/loleaflet/dist/menubar.css +++ b/loleaflet/dist/menubar.css @@ -191,7 +191,7 @@ } /* desktop mode */ -@media (min-width: 769px) and (orientation: portrait),(min-height: 769px) and (orientation: landscape) { +@media (min-width: 769px) { /* hide the button in desktop view */ .main-menu-btn { position: absolute; @@ -220,6 +220,26 @@ } } +@media (max-device-height: 768px) { + .main-menu-btn { + position: relative; + top: 5px; + } + + #main-menu-state:not(:checked) ~ #main-menu { + display: none; + } + + #main-menu { + top: 39px !important; + } + + .main-nav { + position: absolute; + width: 100%; + } +} + /* Some more lo-menu specific customizations */ /* The smartmenus plugin doesn't seem to have support for icons, so implement our own pseudo-elements */ commit ad4e98c2341a8fbc69ae119433a766136bf2da42 Author: Jan Holesovsky <ke...@collabora.com> AuthorDate: Thu Oct 4 00:53:06 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:36:59 2018 +0200 Cleanup the debugging logging. Change-Id: I52ba4be4017a1b4d7a2464c078ec2144511f11f7 Reviewed-on: https://gerrit.libreoffice.org/61338 Reviewed-by: Aron Budea <aron.bu...@collabora.com> Tested-by: Aron Budea <aron.bu...@collabora.com> (cherry picked from commit 6baa59678c34ec1e618ba0a2ac2b74983fbbd858) diff --git a/loleaflet/src/control/Control.MobileInput.js b/loleaflet/src/control/Control.MobileInput.js index 9439f0221..0530cab21 100644 --- a/loleaflet/src/control/Control.MobileInput.js +++ b/loleaflet/src/control/Control.MobileInput.js @@ -89,9 +89,6 @@ L.Control.MobileInput = L.Control.extend({ docLayer = this._map._docLayer, unoKeyCode = handler._toUNOKeyCode(keyCode); - console.log('onKeyEvent: e.type === ' + e.type); - console.log('onKeyEvent: e.keyCode === "' + e.keyCode + '"'); - console.log('onKeyEvent: e.charCode === "' + e.charCode + '"'); this._keyHandled = this._keyHandled || false; if (this._isComposing) { if (keyCode === 229 && charCode === 0) { @@ -138,8 +135,7 @@ L.Control.MobileInput = L.Control.extend({ onCompEvents: function (e) { var map = this._map; - console.log('onCompEvents: e.type === ' + e.type); - console.log('onCompEvents: e.data === "' + e.data + '"'); + if (e.type === 'compositionstart' || e.type === 'compositionupdate') { this._isComposing = true; // we are starting composing with IME this._composingData = e.data; // cache what we have composed so far @@ -154,9 +150,6 @@ L.Control.MobileInput = L.Control.extend({ }, onTextInput: function (e) { - console.log('onTextInput: e.type === ' + e.type); - console.log('onTextInput: e.data === "' + e.data + '"'); - if (!this._keyHandled) { this._textData = e.data; this._textArea.value = ''; @@ -166,9 +159,8 @@ L.Control.MobileInput = L.Control.extend({ }, onInput: function (e) { - console.log('onInput: e.inputType === ' + e.inputType); - var backSpace = this._map.keyboard._toUNOKeyCode(8); + // deferred processing of composition text or normal text; we can get // both in some cases, and based on the input event we need to decide // which one we actually need to use commit 0219af868e2eb63f3736c110292bd229ae4960c7 Author: Jan Holesovsky <ke...@collabora.com> AuthorDate: Thu Oct 4 00:41:34 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:36:54 2018 +0200 android chrome: Make the insertion of . , ! $ and similar chars work. The trick is that actually the 'onInput' (the 'input') event decides what data we should actually use - if those from compose event, or from the text input event. With this, things finally seem to work reasonably well - I am able to insert even emoji :-) Change-Id: I9f19d1e56e4e638cf88b8497abb8eefd24e82cee Reviewed-on: https://gerrit.libreoffice.org/61337 Reviewed-by: Aron Budea <aron.bu...@collabora.com> Tested-by: Aron Budea <aron.bu...@collabora.com> (cherry picked from commit 10bfc4b449577590c4de82cb15d73204be053a3c) diff --git a/loleaflet/src/control/Control.MobileInput.js b/loleaflet/src/control/Control.MobileInput.js index 45920f63a..9439f0221 100644 --- a/loleaflet/src/control/Control.MobileInput.js +++ b/loleaflet/src/control/Control.MobileInput.js @@ -143,9 +143,6 @@ L.Control.MobileInput = L.Control.extend({ if (e.type === 'compositionstart' || e.type === 'compositionupdate') { this._isComposing = true; // we are starting composing with IME this._composingData = e.data; // cache what we have composed so far - if (e.data) { - map._docLayer._postCompositionEvent(0, 'input', e.data); - } } if (e.type === 'compositionend') { @@ -161,17 +158,7 @@ L.Control.MobileInput = L.Control.extend({ console.log('onTextInput: e.data === "' + e.data + '"'); if (!this._keyHandled) { - // Hack for making space in combination with autocompletion text - // input work in Chrome on Andorid. - // - // Chrome (Android) IME triggers keyup/keydown input with - // code 229 when hitting space (as with all composiiton events) - // with addition to 'textinput' event, in which we only see that - // space was entered. - var data = e.data; - if (data.length == 1 && data[0] === ' ') { - map._docLayer._postKeyboardEvent('input', data[0].charCodeAt(), 0); - } + this._textData = e.data; this._textArea.value = ''; } @@ -179,8 +166,25 @@ L.Control.MobileInput = L.Control.extend({ }, onInput: function (e) { + console.log('onInput: e.inputType === ' + e.inputType); + var backSpace = this._map.keyboard._toUNOKeyCode(8); - if (e.inputType && e.inputType === 'deleteContentBackward' && this._lastInput !== backSpace) { + // deferred processing of composition text or normal text; we can get + // both in some cases, and based on the input event we need to decide + // which one we actually need to use + if (e.inputType === 'insertText') { + if (this._textData) { + for (var i = 0; i < this._textData.length; ++i) { + map._docLayer._postKeyboardEvent('input', this._textData[i].charCodeAt(), 0); + } + } + } + else if (e.inputType === 'insertCompositionText') { + if (this._composingData) { + map._docLayer._postCompositionEvent(0, 'input', this._composingData); + } + } + else if (e.inputType === 'deleteContentBackward' && this._lastInput !== backSpace) { // this means we need to delete the entire interim composition; // let's issue backspace that many times if (this._composingData) { @@ -189,6 +193,7 @@ L.Control.MobileInput = L.Control.extend({ } } } + L.DomEvent.stopPropagation(e); } }); commit 678e151e28d8069c6552e6e7b27b0744e080ccec Author: Jan Holesovsky <ke...@collabora.com> AuthorDate: Wed Oct 3 23:49:45 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:36:50 2018 +0200 android chrome: Move the onTextInput handling to a separate method. Change-Id: I1cf091ed9bf82b719ff7bd5170b9e75ecb999d01 Reviewed-on: https://gerrit.libreoffice.org/61336 Reviewed-by: Aron Budea <aron.bu...@collabora.com> Tested-by: Aron Budea <aron.bu...@collabora.com> (cherry picked from commit ea5165794338a05837cc14257ebfcf0fa437c2ab) diff --git a/loleaflet/src/control/Control.MobileInput.js b/loleaflet/src/control/Control.MobileInput.js index 94084371b..45920f63a 100644 --- a/loleaflet/src/control/Control.MobileInput.js +++ b/loleaflet/src/control/Control.MobileInput.js @@ -75,7 +75,8 @@ L.Control.MobileInput = L.Control.extend({ this._textArea.setAttribute('spellcheck', 'false'); L.DomEvent.on(this._textArea, stopEvents, L.DomEvent.stopPropagation) .on(this._textArea, 'keydown keypress keyup', this.onKeyEvents, this) - .on(this._textArea, 'compositionstart compositionupdate compositionend textInput', this.onCompEvents, this) + .on(this._textArea, 'compositionstart compositionupdate compositionend', this.onCompEvents, this) + .on(this._textArea, 'textInput', this.onTextInput, this) .on(this._textArea, 'input', this.onInput, this) .on(this._textArea, 'focus', this.onGotFocus, this) .on(this._textArea, 'blur', this.onLostFocus, this); @@ -152,7 +153,14 @@ L.Control.MobileInput = L.Control.extend({ map._docLayer._postCompositionEvent(0, 'end', ''); } - if (e.type === 'textInput' && !this._keyHandled) { + L.DomEvent.stopPropagation(e); + }, + + onTextInput: function (e) { + console.log('onTextInput: e.type === ' + e.type); + console.log('onTextInput: e.data === "' + e.data + '"'); + + if (!this._keyHandled) { // Hack for making space in combination with autocompletion text // input work in Chrome on Andorid. // @@ -166,6 +174,7 @@ L.Control.MobileInput = L.Control.extend({ } this._textArea.value = ''; } + L.DomEvent.stopPropagation(e); }, commit 96ca59dba4b2c795d5b8878597314acee1422dc9 Author: Jan Holesovsky <ke...@collabora.com> AuthorDate: Wed Oct 3 23:42:30 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:36:46 2018 +0200 debugging: Set noCache when LOOL_SERVE_FROM_FS is set. Makes debugging on Android browser so much easier, as it is otherwise hard to force reload. Change-Id: Iede4fec1c2a110f0ae4b73095ed19f0ee29fa736 Reviewed-on: https://gerrit.libreoffice.org/61335 Reviewed-by: Aron Budea <aron.bu...@collabora.com> Tested-by: Aron Budea <aron.bu...@collabora.com> (cherry picked from commit fde08ea55af98b0374fe872870ebfcedc098c515) diff --git a/wsd/FileServer.cpp b/wsd/FileServer.cpp index 5b3aed55b..35b43c27e 100644 --- a/wsd/FileServer.cpp +++ b/wsd/FileServer.cpp @@ -266,6 +266,10 @@ void FileServerRequestHandler::handleRequest(const HTTPRequest& request, Poco::M try { bool noCache = false; +#if ENABLE_DEBUG + if (std::getenv("LOOL_SERVE_FROM_FS")) + noCache = true; +#endif Poco::Net::HTTPResponse response; Poco::URI requestUri(request.getURI()); LOG_TRC("Fileserver request: " << requestUri.toString()); commit d784a59fb5096385ab30cbd24269c79c8bc76e29 Author: Jan Holesovsky <ke...@collabora.com> AuthorDate: Wed Oct 3 21:52:59 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:36:42 2018 +0200 android chrome: Stop composing when we get an actual key event. So that it is possible to press Enter while still in composition. It is necessary to stop the composing itself, because otherwise the word gets duplicated on the new line as soon as the user presses a key. Change-Id: I78951a423715e71533f1a73d5bbe3b0f0f05e6cd Reviewed-on: https://gerrit.libreoffice.org/61334 Reviewed-by: Aron Budea <aron.bu...@collabora.com> Tested-by: Aron Budea <aron.bu...@collabora.com> (cherry picked from commit f42e0bb4d1c99c1d229a638e59061ef7985d01e8) diff --git a/loleaflet/src/control/Control.MobileInput.js b/loleaflet/src/control/Control.MobileInput.js index 3fab3bf56..94084371b 100644 --- a/loleaflet/src/control/Control.MobileInput.js +++ b/loleaflet/src/control/Control.MobileInput.js @@ -88,10 +88,19 @@ L.Control.MobileInput = L.Control.extend({ docLayer = this._map._docLayer, unoKeyCode = handler._toUNOKeyCode(keyCode); + console.log('onKeyEvent: e.type === ' + e.type); + console.log('onKeyEvent: e.keyCode === "' + e.keyCode + '"'); + console.log('onKeyEvent: e.charCode === "' + e.charCode + '"'); this._keyHandled = this._keyHandled || false; if (this._isComposing) { + if (keyCode === 229 && charCode === 0) { + return; + } + // stop the composing - so that we handle eg. Enter even during + // composition + this._isComposing = false; this._lastInput = null; - return; + this._textArea.value = ''; } docLayer._resetPreFetching(); commit 6db4171f694779b0ba0cf199a576df0cab88ac77 Author: Jan Holesovsky <ke...@collabora.com> AuthorDate: Wed Oct 3 19:46:07 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:36:38 2018 +0200 android chrome: We have to delete the interim results sometimes. When we are autocorrecting a word, we get a deleteContentBackward event; in that case we have to delete everything we have composed so far. Change-Id: I36f3d1afcb9b74ac75dee7d64832cc5a3d346045 Reviewed-on: https://gerrit.libreoffice.org/61333 Reviewed-by: Aron Budea <aron.bu...@collabora.com> Tested-by: Aron Budea <aron.bu...@collabora.com> (cherry picked from commit f176bb1e81b5f3769cb56752c7102f93f2eca024) diff --git a/loleaflet/src/control/Control.MobileInput.js b/loleaflet/src/control/Control.MobileInput.js index 25b0addff..3fab3bf56 100644 --- a/loleaflet/src/control/Control.MobileInput.js +++ b/loleaflet/src/control/Control.MobileInput.js @@ -128,14 +128,13 @@ L.Control.MobileInput = L.Control.extend({ onCompEvents: function (e) { var map = this._map; + console.log('onCompEvents: e.type === ' + e.type); + console.log('onCompEvents: e.data === "' + e.data + '"'); if (e.type === 'compositionstart' || e.type === 'compositionupdate') { this._isComposing = true; // we are starting composing with IME - var txt = ''; - for (var i = 0; i < e.data.length; i++) { - txt += e.data[i]; - } - if (txt) { - map._docLayer._postCompositionEvent(0, 'input', txt); + this._composingData = e.data; // cache what we have composed so far + if (e.data) { + map._docLayer._postCompositionEvent(0, 'input', e.data); } } @@ -152,11 +151,6 @@ L.Control.MobileInput = L.Control.extend({ // code 229 when hitting space (as with all composiiton events) // with addition to 'textinput' event, in which we only see that // space was entered. - // - // TODO: Maybe make sure this is only triggered when keydown has - // 229 code. Also we need to detect that composition was overriden - // (part or whole word deleted) with the spell-checked word. (for - // example: enter 'tar' and with spell-check correct that to 'rat') var data = e.data; if (data.length == 1 && data[0] === ' ') { map._docLayer._postKeyboardEvent('input', data[0].charCodeAt(), 0); @@ -169,7 +163,13 @@ L.Control.MobileInput = L.Control.extend({ onInput: function (e) { var backSpace = this._map.keyboard._toUNOKeyCode(8); if (e.inputType && e.inputType === 'deleteContentBackward' && this._lastInput !== backSpace) { - this._map._docLayer._postKeyboardEvent('input', 0, backSpace); + // this means we need to delete the entire interim composition; + // let's issue backspace that many times + if (this._composingData) { + for (var i = 0; i < this._composingData.length; ++i) { + this._map._docLayer._postKeyboardEvent('input', 0, backSpace); + } + } } L.DomEvent.stopPropagation(e); } commit 51ec3547a6695888b64c32ac7155efadbfa9df34 Author: Jan Holesovsky <ke...@collabora.com> AuthorDate: Wed Oct 3 17:39:38 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:36:34 2018 +0200 android chrome: When autocompleting a word, don't enter it twice. Most of the text input on Android in Chrome works via the composition; only the space has to be entered via textInput. Change-Id: Icd6cea54a962f324215bb6438265e6500f28421d Reviewed-on: https://gerrit.libreoffice.org/61332 Reviewed-by: Aron Budea <aron.bu...@collabora.com> Tested-by: Aron Budea <aron.bu...@collabora.com> (cherry picked from commit 04b858d90fb1de00f312efcbf131bacda5479e7c) diff --git a/loleaflet/src/control/Control.MobileInput.js b/loleaflet/src/control/Control.MobileInput.js index f03b7ddc9..25b0addff 100644 --- a/loleaflet/src/control/Control.MobileInput.js +++ b/loleaflet/src/control/Control.MobileInput.js @@ -145,24 +145,21 @@ L.Control.MobileInput = L.Control.extend({ } if (e.type === 'textInput' && !this._keyHandled) { - // Hack for making space and spell-check text insert work - // in Chrome (on Andorid) or Chrome with IME. + // Hack for making space in combination with autocompletion text + // input work in Chrome on Andorid. // // Chrome (Android) IME triggers keyup/keydown input with // code 229 when hitting space (as with all composiiton events) // with addition to 'textinput' event, in which we only see that - // space was entered. Similar situation is also when inserting - // a soft-keyboard spell-check item - it is visible only with - // 'textinput' event (no composition event is fired). - // To make this work we need to insert textinput.data here.. + // space was entered. // // TODO: Maybe make sure this is only triggered when keydown has // 229 code. Also we need to detect that composition was overriden // (part or whole word deleted) with the spell-checked word. (for // example: enter 'tar' and with spell-check correct that to 'rat') var data = e.data; - for (var idx = 0; idx < data.length; idx++) { - map._docLayer._postKeyboardEvent('input', data[idx].charCodeAt(), 0); + if (data.length == 1 && data[0] === ' ') { + map._docLayer._postKeyboardEvent('input', data[0].charCodeAt(), 0); } this._textArea.value = ''; } commit 0e293cc488cc2637b6361a3caaa49328f2a674e4 Author: Henry Castro <hcas...@collabora.com> AuthorDate: Tue Oct 2 23:47:32 2018 -0400 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:36:29 2018 +0200 loleaflet: mobile: ensure to not create the ruler Change-Id: I7f985f93d8282533aa6cb32bff455b08011d7dfc Reviewed-on: https://gerrit.libreoffice.org/61281 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> (cherry picked from commit acf5ebd540d322077030ed240e7dbba3e14411ea) diff --git a/loleaflet/src/map/Map.js b/loleaflet/src/map/Map.js index 769b2969d..7a078ebd2 100644 --- a/loleaflet/src/map/Map.js +++ b/loleaflet/src/map/Map.js @@ -122,7 +122,7 @@ L.Map = L.Evented.extend({ if (!this.initComplete) { this._fireInitComplete('doclayerinit'); } - if (this._docLayer._docType == 'text') { + if (!L.Browser.mobile && this._docLayer._docType == 'text') { var interactiveRuler = this._permission === 'edit' ? true : false; L.control.ruler({position:'topleft', interactive:interactiveRuler}).addTo(this); } commit 99e7b9f4b24500492a6d4824c7c368dc5d574265 Author: Henry Castro <hcas...@collabora.com> AuthorDate: Tue Oct 2 22:34:01 2018 -0400 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:36:24 2018 +0200 loleafet: mobile: fix max-height screen size tablet Change-Id: I1c869089aee6991de34e06daf0a38d8b07f82a22 Reviewed-on: https://gerrit.libreoffice.org/61280 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> (cherry picked from commit edb617dc94643db2aa18e4762ac306e3e2bf9906) diff --git a/loleaflet/dist/leaflet.css b/loleaflet/dist/leaflet.css index 28de9a21b..332490ed5 100644 --- a/loleaflet/dist/leaflet.css +++ b/loleaflet/dist/leaflet.css @@ -788,7 +788,7 @@ input.clipboard { } } -@media (max-width: 768px) { +@media (max-width: 768px),(max-height: 768px) { .loleaflet-ruler { height: 0px; display: none; diff --git a/loleaflet/dist/loleaflet.css b/loleaflet/dist/loleaflet.css index 07565ff29..e7d38b5eb 100644 --- a/loleaflet/dist/loleaflet.css +++ b/loleaflet/dist/loleaflet.css @@ -81,7 +81,7 @@ body { width: 0; } -@media (max-width: 768px) { +@media (max-width: 768px),(max-height: 768px) { /* Show slidesorter beyond 768px only */ #presentation-controls-wrapper { display: none; diff --git a/loleaflet/dist/menubar.css b/loleaflet/dist/menubar.css index 9649cadd8..7af9d31b6 100644 --- a/loleaflet/dist/menubar.css +++ b/loleaflet/dist/menubar.css @@ -191,7 +191,7 @@ } /* desktop mode */ -@media (min-width: 769px) { +@media (min-width: 769px) and (orientation: portrait),(min-height: 769px) and (orientation: landscape) { /* hide the button in desktop view */ .main-menu-btn { position: absolute; commit 3cc023490e8fa9a07029c6cd40c672b5057a772f Author: Henry Castro <hcas...@collabora.com> AuthorDate: Tue Oct 2 17:16:56 2018 -0400 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:36:19 2018 +0200 loleaflet: mobile fix after hitting back, on-screen keyboard... "permanently" disappears This forces to blur and focus the text area even it is focused because there is no way to determine when the soft on-screen keyboard disappears Change-Id: Ib89ecc42fb795e34032564a62715463dd944c588 Reviewed-on: https://gerrit.libreoffice.org/61277 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> (cherry picked from commit 8c11f8c85ed6404a0e3972b4b905945dc4e210ba) diff --git a/loleaflet/src/control/Control.MobileInput.js b/loleaflet/src/control/Control.MobileInput.js index 8d5f42664..f03b7ddc9 100644 --- a/loleaflet/src/control/Control.MobileInput.js +++ b/loleaflet/src/control/Control.MobileInput.js @@ -34,9 +34,8 @@ L.Control.MobileInput = L.Control.extend({ return; } - if (focus === false) { - this._textArea.blur(); - } else { + this._textArea.blur(); + if (focus !== false) { this._textArea.focus(); } }, commit 3735faabc81cf080d7c10dde45156fe822a157c4 Author: Tamás Zolnai <tamas.zol...@collabora.com> AuthorDate: Mon Oct 1 18:11:25 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:36:15 2018 +0200 wsd: Avoid parsing tile messages twice Change-Id: I049e7ce645999a4d0366ab34ffa75ab0d351947b (cherry picked from commit 8a318cc44d0a96bced7c565544c4772cfb936f93) Reviewed-on: https://gerrit.libreoffice.org/61211 Reviewed-by: Andras Timar <andras.ti...@collabora.com> Tested-by: Andras Timar <andras.ti...@collabora.com> (cherry picked from commit 8f2ea2eba7fbc58b2e2ab5c154e3c9b7a240f59e) diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index 428455f0f..4753db07c 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -13,6 +13,7 @@ #include <fstream> #include <sstream> +#include <memory> #include <Poco/Net/HTTPResponse.h> #include <Poco/StringTokenizer.h> @@ -1027,15 +1028,16 @@ void ClientSession::enqueueSendMessage(const std::shared_ptr<Message>& data) docBroker->assertCorrectThread(); const std::string command = data->firstToken(); + std::unique_ptr<TileDesc> tile; if (command == "tile:") { // Avoid sending tile if it has the same wireID as the previously sent tile - const TileDesc tile = TileDesc::parse(data->firstLine()); - const std::string tileID = generateTileID(tile); + tile.reset(new TileDesc(TileDesc::parse(data->firstLine()))); + const std::string tileID = generateTileID(*tile); auto iter = _oldWireIds.find(tileID); - if(iter != _oldWireIds.end() && tile.getWireId() != 0 && tile.getWireId() == iter->second) + if(iter != _oldWireIds.end() && tile->getWireId() != 0 && tile->getWireId() == iter->second) { - LOG_INF("WSD filters out a tile with the same wireID: " << tile.serialize("tile:")); + LOG_INF("WSD filters out a tile with the same wireID: " << tile->serialize("tile:")); return; } } @@ -1045,10 +1047,9 @@ void ClientSession::enqueueSendMessage(const std::shared_ptr<Message>& data) size_t newSize = _senderQueue.enqueue(data); // Track sent tile - if (command == "tile:") + if (tile) { - const TileDesc tile = TileDesc::parse(data->firstLine()); - traceTileBySend(tile, sizeBefore == newSize); + traceTileBySend(*tile, sizeBefore == newSize); } } commit 0438cc7b665eb63231588656828962c395ad116f Author: Tamás Zolnai <tamas.zol...@collabora.com> AuthorDate: Mon Oct 1 18:04:47 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:36:10 2018 +0200 Remove accidentally pushed log line Change-Id: Icd7fcb96725b3cf7fded199a5eae13ec2c109a58 (cherry picked from commit 835959a617f388541edaf11b210897566b65b754) Reviewed-on: https://gerrit.libreoffice.org/61210 Reviewed-by: Andras Timar <andras.ti...@collabora.com> Tested-by: Andras Timar <andras.ti...@collabora.com> (cherry picked from commit e4766c150ffb1a7a71b93c089bbba0e17c75542f) diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index d28915ec8..428455f0f 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -1049,8 +1049,6 @@ void ClientSession::enqueueSendMessage(const std::shared_ptr<Message>& data) { const TileDesc tile = TileDesc::parse(data->firstLine()); traceTileBySend(tile, sizeBefore == newSize); - if (sizeBefore != newSize) - LOG_INF("Sending new tile to client: " << tile.serialize("tile:")); } } commit a9760131bc969eaee9fc6b4024e5f0a750fc7e12 Author: Jan Holesovsky <ke...@collabora.com> AuthorDate: Tue Oct 2 17:14:33 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:36:02 2018 +0200 calc: We currently cannot paste text/rtf or text/html via LOK... Change-Id: Ic699dddb2cbeaab4c13d030c2f8f3c71eeddac4f Reviewed-on: https://gerrit.libreoffice.org/61256 Reviewed-by: Aron Budea <aron.bu...@collabora.com> Tested-by: Aron Budea <aron.bu...@collabora.com> (cherry picked from commit c648be85f51654302b16fd345471c98421da2cb4) diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js index 33b8632c9..b3f45581d 100644 --- a/loleaflet/src/layer/tile/TileLayer.js +++ b/loleaflet/src/layer/tile/TileLayer.js @@ -2013,12 +2013,22 @@ L.TileLayer = L.GridLayer.extend({ } // now try various mime types - var mimeTypes = [ - ['text/rtf', 'text/rtf'], - ['text/html', 'text/html'], - ['text/plain', 'text/plain;charset=utf-8'], - ['Text', 'text/plain;charset=utf-8'] - ]; + var mimeTypes; + if (this._docType === 'spreadsheet') { + // FIXME apparently we cannot paste the text/html or text/rtf as + // produced by LibreOffice in Calc from some reason + mimeTypes = [ + ['text/plain', 'text/plain;charset=utf-8'], + ['Text', 'text/plain;charset=utf-8'] + ]; + } else { + mimeTypes = [ + ['text/rtf', 'text/rtf'], + ['text/html', 'text/html'], + ['text/plain', 'text/plain;charset=utf-8'], + ['Text', 'text/plain;charset=utf-8'] + ]; + } for (var i = 0; i < mimeTypes.length; ++i) { for (t = 0; t < types.length; ++t) { commit f8fbb2b0b0ca1137b146218491721cf68d58f7a7 Author: Tamás Zolnai <tamas.zol...@collabora.com> AuthorDate: Fri Sep 21 15:49:30 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:35:10 2018 +0200 Fix warning: in case of a text document part number has no meaning In this case the part number is undefined, somewhere it is set to 0 or 1, but has no meaning at all. Just avoid using it with text ducments. Change-Id: Ic98217bf3ea6c86d37c34e42302bf456f7274975 (cherry picked from commit 4b13430f6d63d950c07cb9ceda4e1bca7bec6422) Reviewed-on: https://gerrit.libreoffice.org/60877 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> (cherry picked from commit f707ba7c753ab53ef454fa7c2a6418c4d0b17f77) diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp index eb0440046..33c7ca7c2 100644 --- a/wsd/ClientSession.hpp +++ b/wsd/ClientSession.hpp @@ -133,6 +133,8 @@ public: /// Clear wireId map anytime when client visible area changes (visible area, zoom, part number) void resetWireIdMap(); + + bool isTextDocument() const { return _isTextDocument; } private: /// SocketHandler: disconnection event. diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp index 02b112a95..32e20e8f5 100644 --- a/wsd/DocumentBroker.cpp +++ b/wsd/DocumentBroker.cpp @@ -1357,13 +1357,14 @@ void DocumentBroker::handleTileCombinedRequest(TileCombined& tileCombined, for (const auto& newTile : tileCombined.getTiles()) { const TileDesc& firstOldTile = *(requestedTiles.begin()); - if(newTile.getPart() != firstOldTile.getPart() || - newTile.getWidth() != firstOldTile.getWidth() || - newTile.getHeight() != firstOldTile.getHeight() || - newTile.getTileWidth() != firstOldTile.getTileWidth() || - newTile.getTileHeight() != firstOldTile.getTileHeight() ) + if(!session->isTextDocument() && newTile.getPart() != firstOldTile.getPart()) { - LOG_WRN("Different visible area information in tile requests"); + LOG_WRN("Different part numbers in tile requests"); + } + else if (newTile.getTileWidth() != firstOldTile.getTileWidth() || + newTile.getTileHeight() != firstOldTile.getTileHeight() ) + { + LOG_WRN("Different tile sizes in tile requests"); } bool tileFound = false; commit fa8e499729658245c23621d246067a3b8b7d78ff Author: Aron Budea <aron.bu...@collabora.com> AuthorDate: Wed Sep 26 04:43:38 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:35:04 2018 +0200 Menu: Add Hyperlink to Insert menu in Calc Change-Id: I1401315e0a679adfe074d68a8e3ac2ee45df5e80 Reviewed-on: https://gerrit.libreoffice.org/61181 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> (cherry picked from commit b649919cfaf12377f74fa1958ff3497a8987bf60) diff --git a/loleaflet/src/control/Control.Menubar.js b/loleaflet/src/control/Control.Menubar.js index 5fe17ada1..2b33c4e4f 100644 --- a/loleaflet/src/control/Control.Menubar.js +++ b/loleaflet/src/control/Control.Menubar.js @@ -333,6 +333,7 @@ L.Control.Menubar = L.Control.extend({ {uno: '.uno:InsertObjectChart'}, {name: _UNO('.uno:InsertAnnotation', 'spreadsheet'), id: 'insertcomment', type: 'action'}, {type: 'separator'}, + {uno: '.uno:HyperlinkDialog'}, {uno: '.uno:InsertSymbol'} ]}, {name: _UNO('.uno:FormatMenu', 'spreadsheet'), type: 'menu', menu: [ commit 7cc0034190db40fa555a9442545d64493075728f Author: Henry Castro <hcas...@collabora.com> AuthorDate: Tue Sep 25 09:51:51 2018 -0400 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:35:00 2018 +0200 loleaflet: mobile: fix max-width screen size tablet Change-Id: I3337e669521f0f0f1dd749884f38dd35663491d3 Reviewed-on: https://gerrit.libreoffice.org/60977 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> (cherry picked from commit d1dc66ae5f33e68136a734d5687ab6903a30e442) diff --git a/loleaflet/dist/leaflet.css b/loleaflet/dist/leaflet.css index 2fb0a1925..28de9a21b 100644 --- a/loleaflet/dist/leaflet.css +++ b/loleaflet/dist/leaflet.css @@ -788,7 +788,7 @@ input.clipboard { } } -@media (max-width: 767px) { +@media (max-width: 768px) { .loleaflet-ruler { height: 0px; display: none; diff --git a/loleaflet/dist/loleaflet.css b/loleaflet/dist/loleaflet.css index 68349f2fb..07565ff29 100644 --- a/loleaflet/dist/loleaflet.css +++ b/loleaflet/dist/loleaflet.css @@ -81,7 +81,7 @@ body { width: 0; } -@media (max-width: 767px) { +@media (max-width: 768px) { /* Show slidesorter beyond 768px only */ #presentation-controls-wrapper { display: none; diff --git a/loleaflet/dist/menubar.css b/loleaflet/dist/menubar.css index f192eacbf..9649cadd8 100644 --- a/loleaflet/dist/menubar.css +++ b/loleaflet/dist/menubar.css @@ -191,7 +191,7 @@ } /* desktop mode */ -@media (min-width: 768px) { +@media (min-width: 769px) { /* hide the button in desktop view */ .main-menu-btn { position: absolute; commit 7db188e4b0bfa4b638b5dc508d352f11138c9279 Author: Tamás Zolnai <tamas.zol...@collabora.com> AuthorDate: Fri Sep 28 19:30:57 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:34:54 2018 +0200 Upper limit of sent out versions of the same tile We try to decrease the network usage with avoiding sending out to much tiles to the client. When we already sent out two versions of the same tile without having the tileprocessed message from the client we delay sending out the next version to avoid spamming tiles on the network. Change-Id: Ia47cd7c0d3fb829f6777f0c3265970433591df19 (cherry picked from commit ac2cd92d25815cd476008c6cd8035e989d0c8826) Reviewed-on: https://gerrit.libreoffice.org/61129 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> (cherry picked from commit ea871566685f64949080735d430c7f767c63f582) diff --git a/common/Common.hpp b/common/Common.hpp index 978ef2c18..29008fd24 100644 --- a/common/Common.hpp +++ b/common/Common.hpp @@ -20,6 +20,8 @@ constexpr int CHILD_REBALANCE_INTERVAL_MS = CHILD_TIMEOUT_MS / 10; constexpr int POLL_TIMEOUT_MS = COMMAND_TIMEOUT_MS / 10; constexpr int WS_SEND_TIMEOUT_MS = 1000; +constexpr int TILE_ROUNDTRIP_TIMEOUT_MS = 5000; + /// Pipe and Socket read buffer size. /// Should be large enough for ethernet packets /// which can be 1500 bytes long. diff --git a/test/TileCacheTests.cpp b/test/TileCacheTests.cpp index 535bafc30..f8eefa278 100644 --- a/test/TileCacheTests.cpp +++ b/test/TileCacheTests.cpp @@ -81,6 +81,7 @@ class TileCacheTests : public CPPUNIT_NS::TestFixture //CPPUNIT_TEST(testTileInvalidatePartImpress); CPPUNIT_TEST(testTileBeingRenderedHandling); CPPUNIT_TEST(testWireIDFilteringOnWSDSide); + CPPUNIT_TEST(testLimitTileVersionsOnFly); CPPUNIT_TEST_SUITE_END(); @@ -106,6 +107,7 @@ class TileCacheTests : public CPPUNIT_NS::TestFixture void testTileInvalidatePartImpress(); void testTileBeingRenderedHandling(); void testWireIDFilteringOnWSDSide(); + void testLimitTileVersionsOnFly(); void checkTiles(std::shared_ptr<LOOLWebSocket>& socket, const std::string& type, @@ -1282,6 +1284,66 @@ void TileCacheTests::testWireIDFilteringOnWSDSide() CPPUNIT_ASSERT_MESSAGE("Not expected tile message arrived!", tile.empty()); } +void TileCacheTests::testLimitTileVersionsOnFly() +{ + // We have an upper limit (2) for the versions of the same tile wsd send out + // without getting the tileprocessed message for the first tile message. + const char* testname = "testLimitTileVersionsOnFly "; + + std::string documentPath, documentURL; + getDocumentPathAndURL("empty.odt", documentPath, documentURL, testname); + std::shared_ptr<LOOLWebSocket> socket = loadDocAndGetSocket(_uri, documentURL, testname); + + // Set the client visible area + sendTextFrame(socket, "clientvisiblearea x=-2662 y=0 width=16000 height=9875"); + sendTextFrame(socket, "clientzoom tilepixelwidth=256 tilepixelheight=256 tiletwipwidth=3200 tiletwipheight=3200"); + + // Type one character to trigger sending tiles + sendChar(socket, 'x', skNone, testname); + + // Handle all tiles send by wsd + bool getTileResp = false; + do + { + std::string tile = getResponseString(socket, "tile:", testname, 1000); + getTileResp = !tile.empty(); + } while(getTileResp); + + // Type an other character to trigger sending tiles + sendChar(socket, 'x', skNone, testname); + + // Handle all tiles sent by wsd + getTileResp = false; + do + { + std::string tile = getResponseString(socket, "tile:", testname, 1000); + getTileResp = !tile.empty(); + } while(getTileResp); + + // For the third invalidation wsd does not send the new tile since + // two versions of the same tile were already sent. + sendChar(socket, 'x', skNone, testname); + + std::vector<char> tile1 = getResponseMessage(socket, "tile:", testname, 1000); + CPPUNIT_ASSERT_MESSAGE("Not expected tile message arrived!", tile1.empty()); + + // When the next tileprocessed message arrive with correct tileID + // wsd sends the delayed tile + sendTextFrame(socket, "tileprocessed tile=0:0:0:3200:3200"); + + int arrivedTiles = 0; + bool gotTile = false; + do + { + std::vector<char> tile = getResponseMessage(socket, "tile:", testname, 1000); + gotTile = !tile.empty(); + if(gotTile) + ++arrivedTiles; + } while(gotTile); + + CPPUNIT_ASSERT_EQUAL(1, arrivedTiles); +} + CPPUNIT_TEST_SUITE_REGISTRATION(TileCacheTests); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index 8a4bb18cd..d28915ec8 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -1100,7 +1100,7 @@ void ClientSession::removeOutdatedTilesOnFly() { auto tileIter = _tilesOnFly.begin(); double elapsedTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - tileIter->second).count(); - if(elapsedTimeMs > 5000.0) + if(elapsedTimeMs > TILE_ROUNDTRIP_TIMEOUT_MS) { LOG_WRN("Tracker tileID was dropped because of time out. Tileprocessed message did not arrive"); _tilesOnFly.erase(tileIter); @@ -1110,6 +1110,18 @@ void ClientSession::removeOutdatedTilesOnFly() } } +size_t ClientSession::countIdenticalTilesOnFly(const TileDesc& tile) const +{ + size_t count = 0; + std::string tileID = generateTileID(tile); + for(auto& tileItem : _tilesOnFly) + { + if(tileItem.first == tileID) + ++count; + } + return count; +} + Util::Rectangle ClientSession::getNormalizedVisibleArea() const { Util::Rectangle normalizedVisArea; @@ -1242,7 +1254,7 @@ void ClientSession::handleTileInvalidation(const std::string& message, Util::Rectangle tileRect (j * _tileWidthTwips, i * _tileHeightTwips, _tileWidthTwips, _tileHeightTwips); if(invalidateRect.intersects(tileRect)) { - invalidTiles.emplace_back(TileDesc(part, _tileWidthPixel, _tileHeightPixel, j * _tileWidthTwips, i * _tileHeightTwips, _tileWidthTwips, _tileHeightTwips, -1, 0, -1, false)); + invalidTiles.emplace_back(part, _tileWidthPixel, _tileHeightPixel, j * _tileWidthTwips, i * _tileHeightTwips, _tileWidthTwips, _tileHeightTwips, -1, 0, -1, false); TileWireId oldWireId = 0; auto iter = _oldWireIds.find(generateTileID(invalidTiles.back())); @@ -1329,7 +1341,7 @@ void ClientSession::clearTileSubscription() _tilesBeingRendered.clear(); } -std::string ClientSession::generateTileID(const TileDesc& tile) +std::string ClientSession::generateTileID(const TileDesc& tile) const { std::ostringstream tileID; tileID << tile.getPart() << ":" << tile.getTilePosX() << ":" << tile.getTilePosY() << ":" diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp index 38da74801..eb0440046 100644 --- a/wsd/ClientSession.hpp +++ b/wsd/ClientSession.hpp @@ -110,6 +110,7 @@ public: void clearTilesOnFly(); size_t getTilesOnFlyCount() const { return _tilesOnFly.size(); } void removeOutdatedTilesOnFly(); + size_t countIdenticalTilesOnFly(const TileDesc& tile) const; Util::Rectangle getVisibleArea() const { return _clientVisibleArea; } /// Visible area can have negative value as position, but we have tiles only in the positive range @@ -174,7 +175,7 @@ private: const std::shared_ptr<DocumentBroker>& docBroker); /// Generate a unique id for a tile - std::string generateTileID(const TileDesc& tile); + std::string generateTileID(const TileDesc& tile) const; private: std::weak_ptr<DocumentBroker> _docBroker; diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp index c6b589899..02b112a95 100644 --- a/wsd/DocumentBroker.cpp +++ b/wsd/DocumentBroker.cpp @@ -1424,12 +1424,26 @@ void DocumentBroker::sendRequestedTiles(const std::shared_ptr<ClientSession>& se std::deque<TileDesc>& requestedTiles = session->getRequestedTiles(); if (!requestedTiles.empty()) { + size_t delayedTiles = 0; std::vector<TileDesc> tilesNeedsRendering; while(session->getTilesOnFlyCount() + session->getTilesBeingRenderedCount() < tilesOnFlyUpperLimit && - !requestedTiles.empty()) + !requestedTiles.empty() && + // If we delayed all tiles we don't send any tile (we will when next tileprocessed message arrives) + delayedTiles < requestedTiles.size()) { TileDesc& tile = *(requestedTiles.begin()); + // We already sent out two versions of the same tile, let's not send the third one + // until we get a tileprocessed message for this specific tile. + if (session->countIdenticalTilesOnFly(tile) >= 2) + { + requestedTiles.push_back(requestedTiles.front()); + requestedTiles.pop_front(); + delayedTiles += 1; + LOG_INF("Requested tile was delayed!"); + continue; + } + // Satisfy as many tiles from the cache. std::unique_ptr<std::fstream> cachedTile = _tileCache->lookupTile(tile); if (cachedTile) commit 4d4fb3a2d6f7443bc6653b556e71ee166ed621bc Author: Tamás Zolnai <tamas.zol...@collabora.com> AuthorDate: Wed Sep 26 22:15:41 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:34:49 2018 +0200 Better to have a smaller tiles-on-fly limit It's good if this limit big enough to send all the tiles of the current visible area at once, so the tiles arrive at the same time to the client by zoom or scroll. Now this value is set to a bit bigger so if we have a small amount of tiles before zoom / scroll we'll still see the same on the client side. (cherry picked from commit a1e2acd5effb1c805f06c78040b71d30fb821be4) Change-Id: I8e28dbf6197fe2f683fe9528e9a32c15a191b20e Reviewed-on: https://gerrit.libreoffice.org/61128 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> (cherry picked from commit 6cbd1445cdc6451aa0a93cb8700fe967194228df) diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp index 764054d1b..c6b589899 100644 --- a/wsd/DocumentBroker.cpp +++ b/wsd/DocumentBroker.cpp @@ -1406,7 +1406,7 @@ void DocumentBroker::sendRequestedTiles(const std::shared_ptr<ClientSession>& se std::ceil(normalizedVisArea._y1 / session->getTileHeightInTwips()) + 1; const int tilesInVisArea = tilesFitOnWidth * tilesFitOnHeight; - tilesOnFlyUpperLimit = std::max(TILES_ON_FLY_MIN_UPPER_LIMIT, tilesInVisArea * 1.5f); + tilesOnFlyUpperLimit = std::max(TILES_ON_FLY_MIN_UPPER_LIMIT, tilesInVisArea * 1.1f); } else { commit 2bef97a4773579ae44c0d72b8db1fc71808fd5b8 Author: Tamás Zolnai <tamas.zol...@collabora.com> AuthorDate: Wed Sep 26 22:15:37 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:34:44 2018 +0200 Filter out tiles by wired on wsd side too Some times tiles with the same wireID survives the wireID filtering in kit, so we should do that in wsd too. The issue is with the tilesBeingRendered construction. First when one tile is filtered out on kit side the client remains subcribed to the tile, since wsd does not know filtering happened. Second via the tilesBeingRendered object more clients can be subcribed to the same tile and so when one client request a new version of this tile (with an old wireID) the rendered tile is sent to all subscribed clients even if the other clients has up-to-date tiles. (cherry picked from commit 9683f53a9c81dfcb9624705cbeeed48ec426ca3b) Change-Id: I4ca6b7a83a5d6979a9f924d766a71aba5e5362c7 Reviewed-on: https://gerrit.libreoffice.org/61127 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> (cherry picked from commit 76aa5e07958dc297001a36b26b3d0069f5da4089) diff --git a/test/TileCacheTests.cpp b/test/TileCacheTests.cpp index 2c8f241d8..535bafc30 100644 --- a/test/TileCacheTests.cpp +++ b/test/TileCacheTests.cpp @@ -80,6 +80,7 @@ class TileCacheTests : public CPPUNIT_NS::TestFixture //CPPUNIT_TEST(testTileInvalidatePartCalc); //CPPUNIT_TEST(testTileInvalidatePartImpress); CPPUNIT_TEST(testTileBeingRenderedHandling); + CPPUNIT_TEST(testWireIDFilteringOnWSDSide); CPPUNIT_TEST_SUITE_END(); @@ -104,6 +105,7 @@ class TileCacheTests : public CPPUNIT_NS::TestFixture void testTileInvalidatePartCalc(); void testTileInvalidatePartImpress(); void testTileBeingRenderedHandling(); + void testWireIDFilteringOnWSDSide(); void checkTiles(std::shared_ptr<LOOLWebSocket>& socket, const std::string& type, @@ -1204,6 +1206,82 @@ void TileCacheTests::testTileBeingRenderedHandling() } } +void TileCacheTests::testWireIDFilteringOnWSDSide() +{ + const char* testname = "testWireIDFilteringOnWSDSide "; + + std::string documentPath, documentURL; + getDocumentPathAndURL("empty.odt", documentPath, documentURL, testname); + std::shared_ptr<LOOLWebSocket> socket1 = loadDocAndGetSocket(_uri, documentURL, testname); + // Set the client visible area + sendTextFrame(socket1, "clientvisiblearea x=-4005 y=0 width=50490 height=72300"); + sendTextFrame(socket1, "clientzoom tilepixelwidth=256 tilepixelheight=256 tiletwipwidth=3840 tiletwipheight=3840"); + + std::shared_ptr<LOOLWebSocket> socket2 = loadDocAndGetSocket(_uri, documentURL, testname, true); + // Set the client visible area + sendTextFrame(socket1, "clientvisiblearea x=-4005 y=0 width=50490 height=72300"); + sendTextFrame(socket1, "clientzoom tilepixelwidth=256 tilepixelheight=256 tiletwipwidth=3840 tiletwipheight=3840"); + + //1. First make the first client to trigger the kit to filter out tiles based on identical wireIDs + + // Type one character to trigger invalidation + sendChar(socket1, 'x', skNone, testname); + + // First wsd forwards the invalidation + assertResponseString(socket1, "invalidatetiles:", testname); + + // For the first input wsd will send all invalidated tiles + int arrivedTiles = 0; + bool gotTile = false; + do + { + std::vector<char> tile = getResponseMessage(socket1, "tile:", testname); + gotTile = !tile.empty(); + if(gotTile) + ++arrivedTiles; + } while(gotTile); + + CPPUNIT_ASSERT_MESSAGE("We expect two tiles at least!", arrivedTiles > 1); + + // Type an other character + sendChar(socket1, 'x', skNone, testname); + assertResponseString(socket1, "invalidatetiles:", testname); + + // For the second input wsd will send one tile, since other tiles are indentical + arrivedTiles = 0; + gotTile = false; + do + { + std::vector<char> tile = getResponseMessage(socket1, "tile:", testname); + gotTile = !tile.empty(); + if(gotTile) + ++arrivedTiles; + } while(gotTile); + + CPPUNIT_ASSERT_EQUAL(1, arrivedTiles); + + //2. Now request the same tiles by the other client (e.g. scroll to the same view) + + sendTextFrame(socket2, "tilecombine part=0 width=256 height=256 tileposx=0,3840,7680 tileposy=0,0,0 tilewidth=3840 tileheight=3840"); + + // We expect three tiles sent to the second client + arrivedTiles = 0; + gotTile = false; + do + { + std::vector<char> tile = getResponseMessage(socket2, "tile:", testname); + gotTile = !tile.empty(); + if(gotTile) + ++arrivedTiles; + } while(gotTile); + + CPPUNIT_ASSERT_EQUAL(3, arrivedTiles); + + // wsd should not send tiles messages for the first client + std::vector<char> tile = getResponseMessage(socket1, "tile:", testname); + CPPUNIT_ASSERT_MESSAGE("Not expected tile message arrived!", tile.empty()); +} + CPPUNIT_TEST_SUITE_REGISTRATION(TileCacheTests); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index eb8414ff5..8a4bb18cd 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -1019,6 +1019,41 @@ bool ClientSession::forwardToClient(const std::shared_ptr<Message>& payload) return true; } +void ClientSession::enqueueSendMessage(const std::shared_ptr<Message>& data) +{ + const std::shared_ptr<DocumentBroker> docBroker = _docBroker.lock(); + // If in the correct thread - no need for wakeups. + if (docBroker) + docBroker->assertCorrectThread(); + + const std::string command = data->firstToken(); + if (command == "tile:") + { + // Avoid sending tile if it has the same wireID as the previously sent tile + const TileDesc tile = TileDesc::parse(data->firstLine()); + const std::string tileID = generateTileID(tile); + auto iter = _oldWireIds.find(tileID); + if(iter != _oldWireIds.end() && tile.getWireId() != 0 && tile.getWireId() == iter->second) + { + LOG_INF("WSD filters out a tile with the same wireID: " << tile.serialize("tile:")); + return; + } + } + + LOG_TRC(getName() << " enqueueing client message " << data->id()); + size_t sizeBefore = _senderQueue.size(); + size_t newSize = _senderQueue.enqueue(data); + + // Track sent tile + if (command == "tile:") + { + const TileDesc tile = TileDesc::parse(data->firstLine()); + traceTileBySend(tile, sizeBefore == newSize); + if (sizeBefore != newSize) + LOG_INF("Sending new tile to client: " << tile.serialize("tile:")); + } +} + Authorization ClientSession::getAuthorization() const { Poco::URI::QueryParameters queryParams = _uriPublic.getQueryParameters(); diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp index 67664ee35..38da74801 100644 --- a/wsd/ClientSession.hpp +++ b/wsd/ClientSession.hpp @@ -78,25 +78,7 @@ public: return true; } - void enqueueSendMessage(const std::shared_ptr<Message>& data) - { - const auto docBroker = _docBroker.lock(); - // If in the correct thread - no need for wakeups. - if (docBroker) - docBroker->assertCorrectThread(); - - LOG_TRC(getName() << " enqueueing client message " << data->id()); - size_t sizeBefore = _senderQueue.size(); - size_t newSize = _senderQueue.enqueue(data); - - // Track sent tile - const std::string command = data->firstToken(); - if (command == "tile:") - { - const TileDesc tile = TileDesc::parse(data->firstLine()); - traceTileBySend(tile, sizeBefore == newSize); - } - } + void enqueueSendMessage(const std::shared_ptr<Message>& data); /// Set the save-as socket which is used to send convert-to results. void setSaveAsSocket(const std::shared_ptr<StreamSocket>& socket) diff --git a/wsd/TestStubs.cpp b/wsd/TestStubs.cpp index 9a56e0a67..659d8476d 100644 --- a/wsd/TestStubs.cpp +++ b/wsd/TestStubs.cpp @@ -28,4 +28,6 @@ void ClientSession::traceUnSubscribeToTile(const std::string& /*tileCacheName*/) void ClientSession::clearTileSubscription() {}; +void ClientSession::enqueueSendMessage(const std::shared_ptr<Message>& /*data*/) {}; + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ commit 01c11d240ccbb02356efc4a59ceba32b07f16a8b Author: Tamás Zolnai <tamas.zol...@collabora.com> AuthorDate: Wed Sep 26 22:16:11 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:34:38 2018 +0200 Don't send tiles which was changed outside of the visible area Since this commit: 9473908d45a884827356b504c5f768e2f29cc46b We can avoid that, because the tiles will be invalidated on the client side and when the client visible area changes the invalidated tiles are requested anyway. (cherry picked from commit a1a0bf3718f5dff3996a3ace98a6a9cb0f10f55b) Change-Id: I272e3b4b87380ae574c16a2b480dbc8caabf4b32 Reviewed-on: https://gerrit.libreoffice.org/61126 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> (cherry picked from commit 0726bdf1df4982febd6b0a7cba1c0736be0b488e) diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index b097d5dd3..eb8414ff5 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -1185,9 +1185,14 @@ void ClientSession::handleTileInvalidation(const std::string& message, int part = result.first; Util::Rectangle& invalidateRect = result.second; + // We can ignore the invalidation if it's outside of the visible area + if(!normalizedVisArea.intersects(invalidateRect)) + return; + if( part == -1 ) // If no part is specifed we use the part used by the client part = _clientSelectedPart; + std::vector<TileDesc> invalidTiles; if(part == _clientSelectedPart || _isTextDocument) { commit 9330ed38d70dca1bc41bd86f09f72abe2a8c367e Author: Tamás Zolnai <tamas.zol...@collabora.com> AuthorDate: Wed Sep 26 22:15:30 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:34:32 2018 +0200 Fix tilesBeingRendered tracking by client I changed the code in this commit: c2a5f6acb0f1e93f19104b761661c852d930fb9e To make kit send a tilecombine message even if it does not send actual tile data so we can track that the rendering was done and so we can update the clients' _tilesBeingRendered list. The issue is that tileBeingRendered object belongs to not only one client, but more and so we don't know which client gets the actual empty tile response. So revert this method and rather use a smaller timeout for "waiting" the arrival of the rendered tile. (cherry picked from commit 07e99ad336c87f3d7e211abaaf18639be27c2929) Change-Id: I2dbbab1a62b81cbbb5314f2f37fdbc3415c69130 Reviewed-on: https://gerrit.libreoffice.org/61125 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> (cherry picked from commit 97a5856c5b94c826d0ef987b5b29204fd9d70af3) diff --git a/kit/Kit.cpp b/kit/Kit.cpp index 6d8bae5f8..50ed15435 100644 --- a/kit/Kit.cpp +++ b/kit/Kit.cpp @@ -1034,9 +1034,7 @@ public: // The tile content is identical to what the client already has, so skip it LOG_TRC("Match for tile #" << tileIndex << " at (" << positionX << "," << positionY << ") oldhash==hash (" << hash << "), wireId: " << wireId << " skipping"); - tiles[tileIndex].setWireId(wireId); - tiles[tileIndex].setImgSize(0); - tileIndex++; + tiles.erase(tiles.begin() + tileIndex); continue; } diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index ca43bc426..b097d5dd3 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -50,8 +50,7 @@ ClientSession::ClientSession(const std::string& id, _tileHeightPixel(0), _tileWidthTwips(0), _tileHeightTwips(0), - _isTextDocument(false), - _tilesBeingRendered(0) + _isTextDocument(false) { const size_t curConnections = ++LOOLWSD::NumConnections; LOG_INF("ClientSession ctor [" << getName() << "], current number of connections: " << curConnections); @@ -1275,9 +1274,9 @@ void ClientSession::removeOutdatedTileSubscriptions() while(iterator != _tilesBeingRendered.end()) { double elapsedTime = docBroker->tileCache().getTileBeingRenderedElapsedTimeMs(*iterator); - if(elapsedTime < 0.0 && elapsedTime > 5000.0) + if(elapsedTime < 0.0 && elapsedTime > 200.0) { - LOG_WRN("Tracked TileBeingRendered was dropped because of time out."); + LOG_INF("Tracked TileBeingRendered was dropped because of time out."); _tilesBeingRendered.erase(iterator); } else diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp index 1d1adf746..764054d1b 100644 --- a/wsd/DocumentBroker.cpp +++ b/wsd/DocumentBroker.cpp @@ -1543,16 +1543,25 @@ void DocumentBroker::handleTileCombinedResponse(const std::vector<char>& payload try { - const auto tileCombined = TileCombined::parse(firstLine); - const auto buffer = payload.data(); - auto offset = firstLine.size() + 1; + const size_t length = payload.size(); + if (firstLine.size() < static_cast<std::string::size_type>(length) - 1) + { + const auto tileCombined = TileCombined::parse(firstLine); + const auto buffer = payload.data(); + size_t offset = firstLine.size() + 1; - std::unique_lock<std::mutex> lock(_mutex); + std::unique_lock<std::mutex> lock(_mutex); - for (const auto& tile : tileCombined.getTiles()) + for (const auto& tile : tileCombined.getTiles()) + { + tileCache().saveTileAndNotify(tile, buffer + offset, tile.getImgSize()); + offset += tile.getImgSize(); + } + } + else { - tileCache().saveTileAndNotify(tile, buffer + offset, tile.getImgSize()); - offset += tile.getImgSize(); + LOG_WRN("Dropping empty tilecombine response: " << firstLine); + // They will get re-issued if we don't forget them. } } catch (const std::exception& exc) diff --git a/wsd/TileCache.cpp b/wsd/TileCache.cpp index a00987f3b..250d070a1 100644 --- a/wsd/TileCache.cpp +++ b/wsd/TileCache.cpp @@ -192,17 +192,6 @@ void TileCache::saveTileAndNotify(const TileDesc& tile, const char *data, const std::shared_ptr<TileBeingRendered> tileBeingRendered = findTileBeingRendered(tile); - // Kit did not send image data, because tile has the same wireID as the previously sent tile - // We need to remove only the subscriptions - if(size == 0) - { - if(tileBeingRendered && tileBeingRendered->getVersion() <= tile.getVersion()) - { - forgetTileBeingRendered(tileBeingRendered, tile); - } - return; - } - // Save to disk. const auto cachedName = (tileBeingRendered ? tileBeingRendered->getCacheName() : cacheFileName(tile)); commit 21e1c0efd578ab6219d4280f17ec5beff680148f Author: Tamás Zolnai <tamas.zol...@collabora.com> AuthorDate: Wed Sep 26 22:15:26 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:34:25 2018 +0200 Avoid rendering / sending the same tile twice When we're doing the prerendering we also need to register a tileBeingRendered object, so we know that the given tile will arrive soon. In earlier version of the code, rendering and sending of the tile was not separated in time, but now it is, so we need create a tileBeingRendered object even if we don't subscribe the client to it yet. (cherry picked from commit 5260eae05f89056d4ffc1bdfad1927c3c5f9706d) Change-Id: I1374c22e5ffebe1d6fcc6e37261ddcedeb9066ec Reviewed-on: https://gerrit.libreoffice.org/61124 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> (cherry picked from commit c2d800bd345a14c006697e4fd8a75fcadf36557c) diff --git a/test/TileCacheTests.cpp b/test/TileCacheTests.cpp index ac670792c..2c8f241d8 100644 --- a/test/TileCacheTests.cpp +++ b/test/TileCacheTests.cpp @@ -79,6 +79,7 @@ class TileCacheTests : public CPPUNIT_NS::TestFixture // temporarily disable //CPPUNIT_TEST(testTileInvalidatePartCalc); //CPPUNIT_TEST(testTileInvalidatePartImpress); + CPPUNIT_TEST(testTileBeingRenderedHandling); CPPUNIT_TEST_SUITE_END(); @@ -102,6 +103,7 @@ class TileCacheTests : public CPPUNIT_NS::TestFixture void testTileInvalidateCalc(); void testTileInvalidatePartCalc(); void testTileInvalidatePartImpress(); + void testTileBeingRenderedHandling(); void checkTiles(std::shared_ptr<LOOLWebSocket>& socket, const std::string& type, @@ -1146,6 +1148,62 @@ void TileCacheTests::requestTiles(std::shared_ptr<LOOLWebSocket>& socket, const } } +void TileCacheTests::testTileBeingRenderedHandling() +{ + // The issue here was that we requested the tile of the same tile twice + // and so sometimes we got the same tile message twice from wsd. + const char* testname = "testTileBeingRenderedHandling "; + + std::string documentPath, documentURL; + getDocumentPathAndURL("empty.odt", documentPath, documentURL, testname); + std::shared_ptr<LOOLWebSocket> socket = loadDocAndGetSocket(_uri, documentURL, testname); + + // Set the client visible area + sendTextFrame(socket, "clientvisiblearea x=-2662 y=0 width=16000 height=9875"); + sendTextFrame(socket, "clientzoom tilepixelwidth=256 tilepixelheight=256 tiletwipwidth=3200 tiletwipheight=3200"); + + // Type one character to trigger invalidation + sendChar(socket, 'x', skNone, testname); + + // First wsd forwards the invalidation + assertResponseString(socket, "invalidatetiles:", testname); + + // For the first input wsd will send all invalidated tiles + int arrivedTiles = 0; + bool gotTile = false; + do + { + std::vector<char> tile = getResponseMessage(socket, "tile:", testname); + gotTile = !tile.empty(); + if(gotTile) + ++arrivedTiles; + } while(gotTile); + + CPPUNIT_ASSERT_MESSAGE("We expect two tiles at least!", arrivedTiles > 1); + + // For the later inputs wsd will send one tile, since other ones are indentical + for(int i = 0; i < 10; ++i) + { + // Type an other character + sendChar(socket, 'x', skNone, testname); + assertResponseString(socket, "invalidatetiles:", testname); + + arrivedTiles = 0; + gotTile = false; + do + { + std::vector<char> tile = getResponseMessage(socket, "tile:", testname, 1000); + gotTile = !tile.empty(); + if(gotTile) + ++arrivedTiles; + } while(gotTile); + + CPPUNIT_ASSERT_EQUAL(1, arrivedTiles); + + sendTextFrame(socket, "tileprocessed tile=0:0:0:3200:3200"); + } +} + CPPUNIT_TEST_SUITE_REGISTRATION(TileCacheTests); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp index e01af9069..1d1adf746 100644 --- a/wsd/DocumentBroker.cpp +++ b/wsd/DocumentBroker.cpp @@ -1321,15 +1321,16 @@ void DocumentBroker::handleTileCombinedRequest(TileCombined& tileCombined, std::vector<TileDesc> tilesNeedsRendering; for (auto& tile : tileCombined.getTiles()) { + tile.setVersion(++_tileVersion); std::unique_ptr<std::fstream> cachedTile = _tileCache->lookupTile(tile); if(cachedTile) cachedTile->close(); else { // Not cached, needs rendering. - tile.setVersion(++_tileVersion); tilesNeedsRendering.push_back(tile); _debugRenderedTileCount++; + tileCache().registerTileBeingRendered(tile); } } @@ -1459,9 +1460,13 @@ void DocumentBroker::sendRequestedTiles(const std::shared_ptr<ClientSession>& se else { // Not cached, needs rendering. - tile.setVersion(++_tileVersion); - tilesNeedsRendering.push_back(tile); - _debugRenderedTileCount++; + if (!tileCache().hasTileBeingRendered(tile) || // There is no in progress rendering of the given tile + tileCache().getTileBeingRenderedVersion(tile) < tile.getVersion()) // We need a newer version + { + tile.setVersion(++_tileVersion); + tilesNeedsRendering.push_back(tile); + _debugRenderedTileCount++; + } tileCache().subscribeToTileRendering(tile, session); } requestedTiles.pop_front(); diff --git a/wsd/TileCache.cpp b/wsd/TileCache.cpp index 8e0da8cfe..a00987f3b 100644 --- a/wsd/TileCache.cpp +++ b/wsd/TileCache.cpp @@ -151,6 +151,20 @@ double TileCache::getTileBeingRenderedElapsedTimeMs(const std::string& tileCache return iterator->second->getElapsedTimeMs(); } +bool TileCache::hasTileBeingRendered(const TileDesc& tile) +{ + return (findTileBeingRendered(tile) != nullptr); +} + +int TileCache::getTileBeingRenderedVersion(const TileDesc& tile) +{ + std::shared_ptr<TileBeingRendered> tileBeingRendered = findTileBeingRendered(tile); + if (tileBeingRendered) + return tileBeingRendered->getVersion(); + else + return 0; +} + std::unique_ptr<std::fstream> TileCache::lookupTile(const TileDesc& tile) { if (!_tileCachePersistent) @@ -535,6 +549,29 @@ void TileCache::subscribeToTileRendering(const TileDesc& tile, const std::shared } } +void TileCache::registerTileBeingRendered(const TileDesc& tile) +{ + std::shared_ptr<TileBeingRendered> tileBeingRendered = findTileBeingRendered(tile); + if (tileBeingRendered) + { + const auto duration = (std::chrono::steady_clock::now() - tileBeingRendered->getStartTime()); + if (std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() > COMMAND_TIMEOUT_MS) + { + // Tile painting has stalled. Reissue. + tileBeingRendered->setVersion(tile.getVersion()); + } + } + else + { + const std::string cachedName = cacheFileName(tile); + + assert(_tilesBeingRendered.find(cachedName) == _tilesBeingRendered.end()); + + tileBeingRendered = std::make_shared<TileBeingRendered>(cachedName, tile); + _tilesBeingRendered[cachedName] = tileBeingRendered; + } +} + std::string TileCache::cancelTiles(const std::shared_ptr<ClientSession> &subscriber) { assert(subscriber && "cancelTiles expects valid subscriber"); diff --git a/wsd/TileCache.hpp b/wsd/TileCache.hpp index 146e505ae..3d756fee0 100644 --- a/wsd/TileCache.hpp +++ b/wsd/TileCache.hpp @@ -46,6 +46,11 @@ public: /// Otherwise returns 0 to signify a subscription exists. void subscribeToTileRendering(const TileDesc& tile, const std::shared_ptr<ClientSession>& subscriber); + /// Create the TileBeingRendered object for the given tile indicating that the tile was sent to + /// the kit for rendering. Note: subscribeToTileRendering calls this internally, so you don't need + /// to call this method if you need also to subcribe for the rendered tile. + void registerTileBeingRendered(const TileDesc& tile); + /// Cancels all tile requests by the given subscriber. std::string cancelTiles(const std::shared_ptr<ClientSession>& subscriber); @@ -82,6 +87,9 @@ public: void forgetTileBeingRendered(std::shared_ptr<TileCache::TileBeingRendered> tileBeingRendered, const TileDesc& tile); double getTileBeingRenderedElapsedTimeMs(const std::string& tileCacheName) const; + bool hasTileBeingRendered(const TileDesc& tile); + int getTileBeingRenderedVersion(const TileDesc& tile); + void setThreadOwner(const std::thread::id &id) { _owner = id; } void assertCorrectThread(); commit 4de804fad8438449a6544297ce0258b8916aeac9 Author: Tamás Zolnai <tamas.zol...@collabora.com> AuthorDate: Wed Sep 26 22:15:18 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:34:17 2018 +0200 loleaflet: Reset invalid counter when request new tiles by view change This invalidCount does not work perfectly, since we don't always send tile after every invalidation message (e.g. tile deduplication on severs side, wireID handling). So this invalidCount usage should be reconsidered. Now just avoid to have a tile still invalid after we requested (and get) the newest tile when we are changing the view. Change-Id: I249be63611767af02b04e142bb1c2afcb3a8eebb (cherry picked from commit b7f76d24fb49433c1d6d3b540a33530a482e6df3) Reviewed-on: https://gerrit.libreoffice.org/61123 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> (cherry picked from commit c302c79eab8aa089a58ec2ba411a3580355cda79) diff --git a/loleaflet/src/layer/tile/GridLayer.js b/loleaflet/src/layer/tile/GridLayer.js index a5860a9c0..067509d22 100644 --- a/loleaflet/src/layer/tile/GridLayer.js +++ b/loleaflet/src/layer/tile/GridLayer.js @@ -542,6 +542,9 @@ L.GridLayer = L.Layer.extend({ if (tile && tile.loaded && !invalid) { tile.current = true; newView = false; + } else if (invalid) { + tile._invalidCount = 1; + queue.push(coords); } else { queue.push(coords); } commit 18e00fd07f8de1521e83c826a7e20298d56e2324 Author: Tamás Zolnai <tamas.zol...@collabora.com> AuthorDate: Fri Sep 21 16:04:53 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:34:13 2018 +0200 Use a bigger timeout for waiting tileprocessed message The main purpose of this time-out is to avoid waiting forever for lost tile messages, but since it rare to loose them we can use bigger value, so we can avoid to send new tiles to a slow network. Note that the used time stamp does not mean the time when the tile actually send to the client, but the time when it gets to the sender queue. Change-Id: I230d85c38b3a5dafd195851d0cf4caac23149e3e (cherry picked from commit b623aca57ea47c785ab70ff6ccec58acbff63d75) Reviewed-on: https://gerrit.libreoffice.org/60879 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> (cherry picked from commit 7253dd1499db54550a4e4e887820027c9c8a4c31) diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index 9ef6cf07f..ca43bc426 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -1066,7 +1066,7 @@ void ClientSession::removeOutdatedTilesOnFly() { auto tileIter = _tilesOnFly.begin(); double elapsedTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - tileIter->second).count(); - if(elapsedTimeMs > 3000) + if(elapsedTimeMs > 5000.0) { LOG_WRN("Tracker tileID was dropped because of time out. Tileprocessed message did not arrive"); _tilesOnFly.erase(tileIter); commit ec026416aef31041c7623a0a4cd203721aa05068 Author: Tamás Zolnai <tamas.zol...@collabora.com> AuthorDate: Fri Sep 21 15:52:08 2018 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:34:09 2018 +0200 Convert this warning to info There valid cases when we get unknown tileID. For example we sent some tiles, but in the meantime canceltiles arrives which resets tiles-on-fly list, but the client still send tileprocessed messages for earlier requested tiles. Change-Id: If2ec015106a0e58d66ae4517b64a9552eb8c38fc (cherry picked from commit 0807d04934476a5c27ada9b1938fd7b94147e012) Reviewed-on: https://gerrit.libreoffice.org/60878 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> (cherry picked from commit 0e7fbcb26f7231088d402186500c24992ffa7288) diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index 6461851d9..9ef6cf07f 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -351,7 +351,7 @@ bool ClientSession::_handleInput(const char *buffer, int length) if(iter != _tilesOnFly.end()) _tilesOnFly.erase(iter); else - LOG_WRN("Tileprocessed message with an unknown tile ID"); + LOG_INF("Tileprocessed message with an unknown tile ID"); docBroker->sendRequestedTiles(shared_from_this()); return true; commit b5533d34c2ab75060536caf3ae40889e175f8365 Author: Henry Castro <hcas...@collabora.com> AuthorDate: Thu Sep 20 11:17:38 2018 -0400 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:34:05 2018 +0200 loleaflet: remove docLayer precondition when the map is focused Change-Id: Id7fb2e9d7823a788cb40cd3a953b3f024dd22ee5 Reviewed-on: https://gerrit.libreoffice.org/60826 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> (cherry picked from commit 38fd910bee95c63a6b3f2e82c20f82346d4a526a) diff --git a/loleaflet/src/map/Map.js b/loleaflet/src/map/Map.js index 80460dc59..769b2969d 100644 --- a/loleaflet/src/map/Map.js +++ b/loleaflet/src/map/Map.js @@ -661,11 +661,7 @@ L.Map = L.Evented.extend({ }, focus: function () { - console.debug('focus:'); - if (this._docLayer) { - console.debug('focus: focussing'); - this._clipboardContainer.focus(); - } + this._clipboardContainer.focus(); }, _fireInitComplete: function (condition) { commit 30924be3f36c7940f9c1a85e40111f1f00c387f8 Author: Henry Castro <hcas...@collabora.com> AuthorDate: Wed Sep 19 14:30:32 2018 -0400 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:34:01 2018 +0200 loleaflet: mobile: fix non composition input Change-Id: I6f72f888314baaef00549bb08bcfa78854a0b610 Reviewed-on: https://gerrit.libreoffice.org/60779 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> (cherry picked from commit b8d7dda8c493f135ec62358f0c07579238d10ef9) diff --git a/loleaflet/src/control/Control.MobileInput.js b/loleaflet/src/control/Control.MobileInput.js index 312c99845..8d5f42664 100644 --- a/loleaflet/src/control/Control.MobileInput.js +++ b/loleaflet/src/control/Control.MobileInput.js @@ -91,17 +91,20 @@ L.Control.MobileInput = L.Control.extend({ this._keyHandled = this._keyHandled || false; if (this._isComposing) { + this._lastInput = null; return; } docLayer._resetPreFetching(); if (handler._ignoreKeyEvent({originalEvent: e})) { + this._lastInput = null; // key ignored } else if (e.type === 'keydown') { this._keyHandled = false; if (handler.handleOnKeyDownKeys[keyCode] && charCode === 0) { docLayer._postKeyboardEvent('input', charCode, unoKeyCode); + this._lastInput = unoKeyCode; } } else if ((e.type === 'keypress') && (!handler.handleOnKeyDownKeys[keyCode] || charCode !== 0)) { @@ -113,10 +116,12 @@ L.Control.MobileInput = L.Control.extend({ } docLayer._postKeyboardEvent('input', charCode, unoKeyCode); + this._lastInput = unoKeyCode; this._keyHandled = true; } else if (e.type === 'keyup') { docLayer._postKeyboardEvent('up', charCode, unoKeyCode); + this._lastInput = null; this._keyHandled = true; } L.DomEvent.stopPropagation(e); @@ -166,8 +171,9 @@ L.Control.MobileInput = L.Control.extend({ }, onInput: function (e) { - if (e.inputType && e.inputType === 'deleteContentBackward' && !this._keyHandled) { - this._map._docLayer._postKeyboardEvent('input', 0, this._map.keyboard._toUNOKeyCode(8)); + var backSpace = this._map.keyboard._toUNOKeyCode(8); + if (e.inputType && e.inputType === 'deleteContentBackward' && this._lastInput !== backSpace) { + this._map._docLayer._postKeyboardEvent('input', 0, backSpace); } L.DomEvent.stopPropagation(e); } commit b6087919b5252e68e59d96d68c15f388f716e802 Author: Henry Castro <hcas...@collabora.com> AuthorDate: Wed Sep 19 09:25:29 2018 -0400 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:33:58 2018 +0200 loleaflet: mobile: hide the ruler Change-Id: I87335f3a1116553f1ea548ee759680ffa6533aca Reviewed-on: https://gerrit.libreoffice.org/60767 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> (cherry picked from commit b28dd845004b8931ebfa73525eb3c76e6ed83756) diff --git a/loleaflet/dist/leaflet.css b/loleaflet/dist/leaflet.css index d42802bef..2fb0a1925 100644 --- a/loleaflet/dist/leaflet.css +++ b/loleaflet/dist/leaflet.css @@ -784,6 +784,14 @@ input.clipboard { @media screen and (max-height: 400px) { .loleaflet-ruler { height: 0px; + display: none; + } +} + +@media (max-width: 767px) { + .loleaflet-ruler { + height: 0px; + display: none; } } commit c32b354df29046edffde8f7ed6ba67b03431c774 Author: Henry Castro <hcas...@collabora.com> AuthorDate: Tue Sep 18 21:33:11 2018 -0400 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:33:51 2018 +0200 loleaflet: mobile: fix input text composition events Change-Id: I04446f8c29c4901ebe67e49ae8dffcfac286f300 Reviewed-on: https://gerrit.libreoffice.org/60733 Reviewed-by: Andras Timar <andras.ti...@collabora.com> Tested-by: Andras Timar <andras.ti...@collabora.com> (cherry picked from commit 5b33ec605f9a26d7f7070b2b030f8a7c953f96bd) diff --git a/loleaflet/src/control/Control.MobileInput.js b/loleaflet/src/control/Control.MobileInput.js index 156cb4294..312c99845 100644 --- a/loleaflet/src/control/Control.MobileInput.js +++ b/loleaflet/src/control/Control.MobileInput.js @@ -17,12 +17,16 @@ L.Control.MobileInput = L.Control.extend({ }, onGotFocus: function () { - this._map.addLayer(this._map._docLayer._cursorMarker); + if (this._map._docLayer._cursorMarker) { + this._map.addLayer(this._map._docLayer._cursorMarker); + } }, onLostFocus: function () { - this._textArea.value = ''; - this._map.removeLayer(this._map._docLayer._cursorMarker); + if (this._map._docLayer._cursorMarker) { + this._textArea.value = ''; + this._map.removeLayer(this._map._docLayer._cursorMarker); + } }, focus: function(focus) { @@ -73,6 +77,7 @@ L.Control.MobileInput = L.Control.extend({ L.DomEvent.on(this._textArea, stopEvents, L.DomEvent.stopPropagation) .on(this._textArea, 'keydown keypress keyup', this.onKeyEvents, this) .on(this._textArea, 'compositionstart compositionupdate compositionend textInput', this.onCompEvents, this) + .on(this._textArea, 'input', this.onInput, this) .on(this._textArea, 'focus', this.onGotFocus, this) .on(this._textArea, 'blur', this.onLostFocus, this); }, @@ -85,6 +90,10 @@ L.Control.MobileInput = L.Control.extend({ unoKeyCode = handler._toUNOKeyCode(keyCode); this._keyHandled = this._keyHandled || false; + if (this._isComposing) { + return; + } + docLayer._resetPreFetching(); if (handler._ignoreKeyEvent({originalEvent: e})) { // key ignored @@ -128,11 +137,6 @@ L.Control.MobileInput = L.Control.extend({ if (e.type === 'compositionend') { this._isComposing = false; // stop of composing with IME - // get the composited char codes - // clear the input now - best to do this ASAP so the input - // is clear for the next word - //map._clipboardContainer.setValue(''); - // Set all keycodes to zero map._docLayer._postCompositionEvent(0, 'end', ''); } @@ -156,6 +160,14 @@ L.Control.MobileInput = L.Control.extend({ for (var idx = 0; idx < data.length; idx++) { map._docLayer._postKeyboardEvent('input', data[idx].charCodeAt(), 0); } + this._textArea.value = ''; + } + L.DomEvent.stopPropagation(e); + }, + + onInput: function (e) { + if (e.inputType && e.inputType === 'deleteContentBackward' && !this._keyHandled) { + this._map._docLayer._postKeyboardEvent('input', 0, this._map.keyboard._toUNOKeyCode(8)); } L.DomEvent.stopPropagation(e); } commit ea81916dabfe03c4871afae693839d8a178971c4 Author: Henry Castro <hcas...@collabora.com> AuthorDate: Sun Aug 26 11:55:35 2018 -0400 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Fri Oct 5 20:33:47 2018 +0200 loleaflet: mobile: add control to handle keyboard events Change-Id: I978e388eb1066374bd0174e35211bd3bd5a6743b Reviewed-on: https://gerrit.libreoffice.org/60651 Reviewed-by: Jan Holesovsky <ke...@collabora.com> Tested-by: Jan Holesovsky <ke...@collabora.com> (cherry picked from commit d7a2a4c257accfae655ede2046da3aa1225c95fd) diff --git a/loleaflet/build/deps.js b/loleaflet/build/deps.js index bac393583..2f95c54d4 100644 --- a/loleaflet/build/deps.js +++ b/loleaflet/build/deps.js @@ -290,6 +290,13 @@ var deps = { desc: 'Column Header bar' }, + ControlMobileInput: { + src: ['control/Control.js', + 'control/Control.MobileInput.js'], + heading: 'Controls', + desc: 'Mobile Input' + }, + ControlRowHeader: { src: ['control/Control.js', 'control/Control.RowHeader.js'], diff --git a/loleaflet/dist/loleaflet.css b/loleaflet/dist/loleaflet.css index e220f5126..68349f2fb 100644 --- a/loleaflet/dist/loleaflet.css +++ b/loleaflet/dist/loleaflet.css @@ -140,6 +140,19 @@ body { overflow: auto; } +.loleaflet-mobile-container { + top: 30px; + margin: 0; + width: 1px; + opacity: 0; +} + +.loleaflet-mobile-input { + width: 1px; + padding: 0px; + border: 0px; +} + /* Important to override context-menu-icon's font-family here otherwise, jquery-contextmenu.css * will try to load its own font file which is not available in dist/ */ .context-menu-icon::before { diff --git a/loleaflet/src/control/Control.MobileInput.js b/loleaflet/src/control/Control.MobileInput.js new file mode 100644 index 000000000..156cb4294 --- /dev/null +++ b/loleaflet/src/control/Control.MobileInput.js @@ -0,0 +1,166 @@ +/* -*- js-indent-level: 8 -*- */ +/* + * L.Control.MobileInput. + */ +L.Control.MobileInput = L.Control.extend({ + options: { + position: 'topleft' + }, + + initialize: function (options) { + L.setOptions(this, options); + }, + + onAdd: function () { + this._initLayout(); + return this._container; + }, + + onGotFocus: function () { + this._map.addLayer(this._map._docLayer._cursorMarker); + }, + + onLostFocus: function () { + this._textArea.value = ''; + this._map.removeLayer(this._map._docLayer._cursorMarker); + }, + + focus: function(focus) { + if (this._map._permission !== 'edit') { + return; + } + + if (focus === false) { + this._textArea.blur(); + } else { + this._textArea.focus(); + } + }, + + select: function() { + this._textArea.select(); + }, + + getValue: function() { + return this._textArea.value; + }, + + setValue: function(val) { + this._textArea.value = val; + }, + + activeElement: function () { + return this._textArea; + }, + + showCursor: function () { + if (this._textArea === document.activeElement) { + this.onGotFocus(); + } + }, + + _initLayout: function () { + var constOff = 'off', + stopEvents = 'touchstart touchmove touchend mousedown mousemove mouseout mouseover mouseup mousewheel click scroll', + container = this._container = L.DomUtil.create('div', 'loleaflet-mobile-container'); + + this._textArea = L.DomUtil.create('input', 'loleaflet-mobile-input', container); + this._textArea.setAttribute('type', 'text'); + this._textArea.setAttribute('autocorrect', constOff); + this._textArea.setAttribute('autocapitalize', constOff); + this._textArea.setAttribute('autocomplete', constOff); + this._textArea.setAttribute('spellcheck', 'false'); + L.DomEvent.on(this._textArea, stopEvents, L.DomEvent.stopPropagation) + .on(this._textArea, 'keydown keypress keyup', this.onKeyEvents, this) + .on(this._textArea, 'compositionstart compositionupdate compositionend textInput', this.onCompEvents, this) + .on(this._textArea, 'focus', this.onGotFocus, this) + .on(this._textArea, 'blur', this.onLostFocus, this); + }, + + onKeyEvents: function (e) { + var keyCode = e.keyCode, + charCode = e.charCode, + handler = this._map.keyboard, + docLayer = this._map._docLayer, + unoKeyCode = handler._toUNOKeyCode(keyCode); + + this._keyHandled = this._keyHandled || false; + docLayer._resetPreFetching(); + if (handler._ignoreKeyEvent({originalEvent: e})) { + // key ignored + } + else if (e.type === 'keydown') { + this._keyHandled = false; + if (handler.handleOnKeyDownKeys[keyCode] && charCode === 0) { + docLayer._postKeyboardEvent('input', charCode, unoKeyCode); + } + } + else if ((e.type === 'keypress') && (!handler.handleOnKeyDownKeys[keyCode] || charCode !== 0)) { + if (charCode === keyCode && charCode !== 13) { + // Chrome sets keyCode = charCode for printable keys + // while LO requires it to be 0 + keyCode = 0; + unoKeyCode = handler._toUNOKeyCode(keyCode); + } + + docLayer._postKeyboardEvent('input', charCode, unoKeyCode); + this._keyHandled = true; + } + else if (e.type === 'keyup') { + docLayer._postKeyboardEvent('up', charCode, unoKeyCode); + this._keyHandled = true; + } + L.DomEvent.stopPropagation(e); + }, + + onCompEvents: function (e) { + var map = this._map; + if (e.type === 'compositionstart' || e.type === 'compositionupdate') { + this._isComposing = true; // we are starting composing with IME + var txt = ''; + for (var i = 0; i < e.data.length; i++) { + txt += e.data[i]; + } + if (txt) { + map._docLayer._postCompositionEvent(0, 'input', txt); + } + } + + if (e.type === 'compositionend') { + this._isComposing = false; // stop of composing with IME + // get the composited char codes + // clear the input now - best to do this ASAP so the input + // is clear for the next word + //map._clipboardContainer.setValue(''); + // Set all keycodes to zero + map._docLayer._postCompositionEvent(0, 'end', ''); + } + + if (e.type === 'textInput' && !this._keyHandled) { + // Hack for making space and spell-check text insert work + // in Chrome (on Andorid) or Chrome with IME. + // + // Chrome (Android) IME triggers keyup/keydown input with + // code 229 when hitting space (as with all composiiton events) + // with addition to 'textinput' event, in which we only see that + // space was entered. Similar situation is also when inserting + // a soft-keyboard spell-check item - it is visible only with + // 'textinput' event (no composition event is fired). + // To make this work we need to insert textinput.data here.. + // + // TODO: Maybe make sure this is only triggered when keydown has + // 229 code. Also we need to detect that composition was overriden + // (part or whole word deleted) with the spell-checked word. (for + // example: enter 'tar' and with spell-check correct that to 'rat') + var data = e.data; + for (var idx = 0; idx < data.length; idx++) { + map._docLayer._postKeyboardEvent('input', data[idx].charCodeAt(), 0); + } + } + L.DomEvent.stopPropagation(e); + } +}); + +L.control.mobileInput = function (options) { + return new L.Control.MobileInput(options); +}; diff --git a/loleaflet/src/map/Map.js b/loleaflet/src/map/Map.js index 8cc34c7c6..80460dc59 100644 --- a/loleaflet/src/map/Map.js +++ b/loleaflet/src/map/Map.js @@ -87,8 +87,12 @@ L.Map = L.Evented.extend({ this._socket = L.socket(this); this._progressBar = L.progressOverlay(this.getCenter(), L.point(150, 25)); - this._clipboardContainer = L.clipboardContainer(); - this.addLayer(this._clipboardContainer); + if (L.Browser.mobile) { + this._clipboardContainer = L.control.mobileInput().addTo(this); + } else { + this._clipboardContainer = L.clipboardContainer(); + this.addLayer(this._clipboardContainer); + } // When all these conditions are met, fire statusindicator:initializationcomplete this.initConditions = { commit 64eb99ebf681c71102ebf258dd3bf358501d8628 Author: Henry Castro <hcas...@collabora.com> AuthorDate: Mon Sep 17 23:28:13 2018 -0400 ... etc. - the rest is truncated _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits