[Libreoffice-commits] online.git: Branch 'feature/mobile-input' - 14 commits - bundled/include kit/ChildSession.cpp kit/ChildSession.hpp loleaflet/src wsd/ClientSession.cpp

Iván Sánchez Ortega (via logerrit) logerrit at kemper.freedesktop.org
Thu Oct 3 12:57:43 UTC 2019


 bundled/include/LibreOfficeKit/LibreOfficeKit.h   |    6 
 bundled/include/LibreOfficeKit/LibreOfficeKit.hxx |   13 
 kit/ChildSession.cpp                              |   28 
 kit/ChildSession.hpp                              |    1 
 loleaflet/src/control/Control.LokDialog.js        |    9 
 loleaflet/src/layer/marker/ClipboardContainer.js  |  795 +++++++---------------
 loleaflet/src/map/handler/Map.Keyboard.js         |    6 
 wsd/ClientSession.cpp                             |    3 
 8 files changed, 336 insertions(+), 525 deletions(-)

New commits:
commit 6b783bcfd6223a4fef31411dbc2917c624cc55c6
Author:     Iván Sánchez Ortega <ivan.sanchez at collabora.com>
AuthorDate: Mon Jul 29 17:46:42 2019 +0200
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 13:56:08 2019 +0100

    loleaflet: preventDefault() on lokDialog clicks to avoid focus changes.
    
    Change-Id: I95c3f94562cbfd0de71cd7330fa0b1baf1562a21

diff --git a/loleaflet/src/control/Control.LokDialog.js b/loleaflet/src/control/Control.LokDialog.js
index 1b65b1086..b2fd267a2 100644
--- a/loleaflet/src/control/Control.LokDialog.js
+++ b/loleaflet/src/control/Control.LokDialog.js
@@ -615,7 +615,7 @@ L.Control.LokDialog = L.Control.extend({
 		}, this);
 
 		L.DomEvent.on(canvas, 'mousedown mouseup', function(e) {
-			L.DomEvent.stopPropagation(e);
+			L.DomEvent.stop(e);
 			var buttons = 0;
 			if (this._map['mouse']) {
 				buttons |= e.button === this._map['mouse'].JSButtons.left ? this._map['mouse'].LOButtons.left : 0;
@@ -629,6 +629,12 @@ L.Control.LokDialog = L.Control.extend({
 			this._postWindowMouseEvent(lokEventType, id, e.offsetX, e.offsetY, 1, buttons, 0);
 			//dlgInput.focus();
 		}, this);
+
+		L.DomEvent.on(canvas, 'click', function(ev) {
+			// Clicking on the dialog's canvas shall not trigger any
+			// focus change - therefore the event is stopped and preventDefault()ed.
+			L.DomEvent.stop(ev);
+		});
 	},
 
 	_setupGestures: function(dialogContainer, id, canvas) {
commit 6fc7686ff8b7340f2c0ba675a8b34dd8205b3471
Author:     Marco Cecchetti <mrcekets at gmail.com>
AuthorDate: Tue Jul 23 21:50:55 2019 +0200
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 13:55:08 2019 +0100

    android-chrome: keyboard disappears when tapping on current input field
    
    Change-Id: I76145a4c4eeba6f1cb9ae3f05784d426ea298ccc

diff --git a/loleaflet/src/control/Control.LokDialog.js b/loleaflet/src/control/Control.LokDialog.js
index 5047e03db..1b65b1086 100644
--- a/loleaflet/src/control/Control.LokDialog.js
+++ b/loleaflet/src/control/Control.LokDialog.js
@@ -386,6 +386,7 @@ L.Control.LokDialog = L.Control.extend({
 		// set the position of the cursor container element
 		L.DomUtil.setStyle(this._dialogs[dlgId].cursor, 'left', x + 'px');
 		L.DomUtil.setStyle(this._dialogs[dlgId].cursor, 'top', y + 'px');
+		this._map.getClipboardContainer().focus();
 	},
 
 	_createDialogCursor: function(dialogId) {
commit 3dfd05f9c171d47ae3663b2ba9d116c5f9f0b708
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Tue Jul 23 20:41:16 2019 +0100
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 13:54:56 2019 +0100

    input: use pre-pended non-blanking space & handle GBoard better.
    
    GBoard's unwelcome & unwanted movement of our cursor does not
    necessarily mean backspace - so special case detect that.
    
    Also use pre-pended   on Android, apparently it stops GBoard
    capitalizing everything, hmm.
    
    Change-Id: Idfbc696c3e45f173895ed0f26abaea3a32ad86c0

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index 3e16778ef..7e048af14 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -25,8 +25,13 @@ L.ClipboardContainer = L.Layer.extend({
 		this._hasWorkingSelectionStart = undefined; // does it work ?
 		this._ignoreNextBackspace = false;
 
+		this._preSpaceChar = ' ';
 		// Might need to be \xa0 in some legacy browsers ?
-		this._spaceChar = ' ';
+		if (L.Browser.android && L.Browser.webkit) {
+			// fool GBoard into not auto-capitalizing constantly
+			this._preSpaceChar = '\xa0';
+		}
+		this._postSpaceChar = ' ';
 
 		// Debug flag, used in fancyLog(). See the debug() method.
 //		this._isDebugOn = true;
@@ -370,19 +375,23 @@ L.ClipboardContainer = L.Layer.extend({
 	// Backspaces and deletes at the beginning / end are filtered out, so
 	// we get a beforeinput, but no input for them. Sometimes we can end up
 	// in a state where we lost our leading / terminal chars and can't recover
-	_onBeforeInput: function _onBeforeInput(/* ev */) {
+	_onBeforeInput: function _onBeforeInput(ev) {
 		this._ignoreNextBackspace = false;
 		if (this._hasWorkingSelectionStart) {
 			var value = this._textArea.value;
-			if (value.length == 2 && value === this._spaceChar + this._spaceChar &&
+			if (value.length == 2 && value === this._preSpaceChar + this._postSpaceChar &&
 			    this._textArea.selectionStart === 0)
 			{
 				// It seems some inputs eg. GBoard can magically move the cursor from " | " to "|  "
 				console.log('Oh dear, gboard sabotaged our cursor position, fixing');
-				this._removeTextContent(1, 0);
+				// But when we detect the problem only emit a delete when we have one.
+				if (ev.inputType && ev.inputType === 'deleteContentBackward')
+				{
+					this._removeTextContent(1, 0);
+					// Having mended it we now get a real backspace on input (sometimes)
+					this._ignoreNextBackspace = true;
+				}
 				this._emptyArea();
-				// Having mended it we now get a real backspace on input (sometimes)
-				this._ignoreNextBackspace = true;
 			}
 		}
 	},
@@ -405,19 +414,24 @@ L.ClipboardContainer = L.Layer.extend({
 				this._deleteHint = '';
 		}
 
+		var ignoreBackspace = this._ignoreNextBackspace;
+		this._ignoreNextBackspace = false;
+
 		var content = this.getValueAsCodePoints();
 
-		var spaceChar = this._spaceChar.charCodeAt(0);
+		var preSpaceChar = this._preSpaceChar.charCodeAt(0);
+		var postSpaceChar = this._postSpaceChar.charCodeAt(0);
 
 		// We use a different leading and terminal space character
 		// to differentiate backspace from delete, then replace the character.
-		if (content.length < 1 || content[0] !== spaceChar) { // missing initial space
+		if (content.length < 1 || content[0] !== preSpaceChar) { // missing initial space
 			console.log('Sending backspace');
-			this._removeTextContent(1, 0);
+			if (!ignoreBackspace)
+				this._removeTextContent(1, 0);
 			this._emptyArea();
 			return;
 		}
-		if (content[content.length-1] !== spaceChar) { // missing trailing space.
+		if (content[content.length-1] !== postSpaceChar) { // missing trailing space.
 			console.log('Sending delete');
 			this._removeTextContent(0, 1);
 			this._emptyArea();
@@ -428,9 +442,8 @@ L.ClipboardContainer = L.Layer.extend({
 			if (this._deleteHint == 'backspace' ||
 			    this._textArea.selectionStart === 0)
 			{
-				if (!this._ignoreNextBackspace)
+				if (!ignoreBackspace)
 					this._removeTextContent(1, 0);
-				this._ignoreNextBackspace = false;
 			}
 			else if (this._deleteHint == 'delete' ||
 				 this._textArea.selectionStart === 1)
@@ -537,7 +550,7 @@ L.ClipboardContainer = L.Layer.extend({
 		console.log('Set old/lastContent to empty');
 		this._lastContent = [];
 
-		this._textArea.value = this._spaceChar + this._spaceChar;
+		this._textArea.value = this._preSpaceChar + this._postSpaceChar;
 		this._textArea.setSelectionRange(1, 1);
 		if (this._hasWorkingSelectionStart === undefined)
 			this._hasWorkingSelectionStart = (this._textArea.selectionStart === 1);
commit 390ece4d59e08cf28f48d1dd52aa84d116cc6a7d
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Tue Jul 23 18:19:40 2019 +0100
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 13:54:51 2019 +0100

    input: remove Gecko special-case breaking new-line input.
    
    Seems we can unify this, and let the textarea handle enters.
    
    Change-Id: I4ff020993ba562fb95899c3ff113dfc835f7b419

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index f24720858..3e16778ef 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -493,14 +493,10 @@ L.ClipboardContainer = L.Layer.extend({
 		// MSIE/Edge cannot compare a string to "\n" for whatever reason,
 		// so compare charcode as well
 		if (text === '\n' || (text.length === 1 && text.charCodeAt(0) === 13)) {
-			// we get a duplicate key-event on Gecko, oddly so drop it.
-			if (!L.Browser.gecko)
-			{
-				// The composition messages doesn't play well with just a line break,
-				// therefore send a keystroke.
-				this._sendKeyEvent(13, 1280);
-				this._emptyArea();
-			}
+			// The composition messages doesn't play well with just a line break,
+			// therefore send a keystroke.
+			this._sendKeyEvent(13, 1280);
+			this._emptyArea();
 		} else {
 			// The composition messages doesn't play well with line breaks inside
 			// the composed word (e.g. word and a newline are queued client-side
diff --git a/loleaflet/src/map/handler/Map.Keyboard.js b/loleaflet/src/map/handler/Map.Keyboard.js
index 1e5299ef7..fb0f8954f 100644
--- a/loleaflet/src/map/handler/Map.Keyboard.js
+++ b/loleaflet/src/map/handler/Map.Keyboard.js
@@ -338,10 +338,10 @@ L.Map.Keyboard = L.Handler.extend({
 				}
 			}
 			else if ((ev.type === 'keypress') && (!this.handleOnKeyDownKeys[keyCode] || charCode !== 0)) {
-				if (keyCode === 8 || keyCode === 46)
+				if (keyCode === 8 || keyCode === 46 || keyCode === 13)
 				{
 					// handled generically in ClipboardContainer.js
-					console.log('Ignore backspace/delete keypress');
+					console.log('Ignore backspace/delete/enter keypress');
 					return;
 				}
 				if (charCode === keyCode && charCode !== 13) {
commit 005bc16a48b10de38fe6d8418d290f634627ccdf
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Tue Jul 23 17:59:43 2019 +0100
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 13:54:47 2019 +0100

    input: ensure we emit a backspace as we repair for gboard.
    
    But don't let ourselves emit another one with in an input call later.
    
    Change-Id: Ic4443b5e6d5a5dbb1ac5381328134af98739b299

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index e2c0e7602..f24720858 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -23,6 +23,7 @@ L.ClipboardContainer = L.Layer.extend({
 		// Content
 		this._lastContent = []; // unicode characters
 		this._hasWorkingSelectionStart = undefined; // does it work ?
+		this._ignoreNextBackspace = false;
 
 		// Might need to be \xa0 in some legacy browsers ?
 		this._spaceChar = ' ';
@@ -322,6 +323,7 @@ L.ClipboardContainer = L.Layer.extend({
 		if (this._isDebugOn) {
 			var state = this._isComposing ? 'C' : 'N';
 			state += this._hasWorkingSelectionStart ? 'S' : '-';
+			state += this._ignoreNextBackspace ? 'I' : '-';
 			state += ' ';
 
 			var textSel = this._textArea.selectionStart + '!' + this._textArea.selectionEnd;
@@ -369,14 +371,18 @@ L.ClipboardContainer = L.Layer.extend({
 	// we get a beforeinput, but no input for them. Sometimes we can end up
 	// in a state where we lost our leading / terminal chars and can't recover
 	_onBeforeInput: function _onBeforeInput(/* ev */) {
+		this._ignoreNextBackspace = false;
 		if (this._hasWorkingSelectionStart) {
-			if (this._textArea.length == 2 &&
-			    this._textArea.value === this._spaceChar + this._spaceChar &&
+			var value = this._textArea.value;
+			if (value.length == 2 && value === this._spaceChar + this._spaceChar &&
 			    this._textArea.selectionStart === 0)
 			{
 				// It seems some inputs eg. GBoard can magically move the cursor from " | " to "|  "
 				console.log('Oh dear, gboard sabotaged our cursor position, fixing');
+				this._removeTextContent(1, 0);
 				this._emptyArea();
+				// Having mended it we now get a real backspace on input (sometimes)
+				this._ignoreNextBackspace = true;
 			}
 		}
 	},
@@ -421,7 +427,11 @@ L.ClipboardContainer = L.Layer.extend({
 			console.log('Missing terminal nodes: ' + this._deleteHint);
 			if (this._deleteHint == 'backspace' ||
 			    this._textArea.selectionStart === 0)
-				this._removeTextContent(1, 0);
+			{
+				if (!this._ignoreNextBackspace)
+					this._removeTextContent(1, 0);
+				this._ignoreNextBackspace = false;
+			}
 			else if (this._deleteHint == 'delete' ||
 				 this._textArea.selectionStart === 1)
 				this._removeTextContent(0, 1);
commit 4e8b529604c9feb9948f278fc0a903624dceaebc
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Tue Jul 23 17:35:50 2019 +0100
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 13:54:43 2019 +0100

    input: track if we have a working selectionStart & correct problems.
    
    It -seems- that GBoard can move the cursor selection in some cases.
    
    Adding the test code to fetch the cursor position seems to avoid it
    being called, possibly timing sensitive.
    
    Change-Id: I97a3bceec2169e8c15b8157a3c3eca1005a69172

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index e244e2495..e2c0e7602 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -22,7 +22,7 @@ L.ClipboardContainer = L.Layer.extend({
 
 		// Content
 		this._lastContent = []; // unicode characters
-		this._lastCursor = 1;   // last cursor position.
+		this._hasWorkingSelectionStart = undefined; // does it work ?
 
 		// Might need to be \xa0 in some legacy browsers ?
 		this._spaceChar = ' ';
@@ -103,6 +103,7 @@ L.ClipboardContainer = L.Layer.extend({
 		);
 
 		onoff(this._textArea, 'input', this._onInput, this);
+		onoff(this._textArea, 'beforeinput', this._onBeforeInput, this);
 		onoff(this._textArea, 'compositionstart', this._onCompositionStart, this);
 		onoff(this._textArea, 'compositionupdate', this._onCompositionUpdate, this);
 		onoff(this._textArea, 'compositionend', this._onCompositionEnd, this);
@@ -320,6 +321,7 @@ L.ClipboardContainer = L.Layer.extend({
 		// Pretty-print on console (but only if "tile layer debug mode" is active)
 		if (this._isDebugOn) {
 			var state = this._isComposing ? 'C' : 'N';
+			state += this._hasWorkingSelectionStart ? 'S' : '-';
 			state += ' ';
 
 			var textSel = this._textArea.selectionStart + '!' + this._textArea.selectionEnd;
@@ -363,6 +365,22 @@ L.ClipboardContainer = L.Layer.extend({
 		}
 	},
 
+	// Backspaces and deletes at the beginning / end are filtered out, so
+	// we get a beforeinput, but no input for them. Sometimes we can end up
+	// in a state where we lost our leading / terminal chars and can't recover
+	_onBeforeInput: function _onBeforeInput(/* ev */) {
+		if (this._hasWorkingSelectionStart) {
+			if (this._textArea.length == 2 &&
+			    this._textArea.value === this._spaceChar + this._spaceChar &&
+			    this._textArea.selectionStart === 0)
+			{
+				// It seems some inputs eg. GBoard can magically move the cursor from " | " to "|  "
+				console.log('Oh dear, gboard sabotaged our cursor position, fixing');
+				this._emptyArea();
+			}
+		}
+	},
+
 	// Fired when text has been inputed, *during* and after composing/spellchecking
 	_onInput: function _onInput(ev) {
 		this._map.notifyActive();
@@ -448,7 +466,6 @@ L.ClipboardContainer = L.Layer.extend({
 			newText = newText.slice(matchTo);
 
 		this._lastContent = content;
-		this._lastCursor = this._textArea.selectionStart;
 
 		if (newText.length > 0)
 			this._sendText(String.fromCharCode.apply(null, newText));
@@ -515,9 +532,11 @@ L.ClipboardContainer = L.Layer.extend({
 		this._lastContent = [];
 
 		this._textArea.value = this._spaceChar + this._spaceChar;
-		/// TODO: Check that this selection method works with MSIE11
 		this._textArea.setSelectionRange(1, 1);
-		this._lastCursor = 1;
+		if (this._hasWorkingSelectionStart === undefined)
+			this._hasWorkingSelectionStart = (this._textArea.selectionStart === 1);
+
+		this._fancyLog('empty-area-end');
 
 		this._ignoreInputCount--;
 	},
commit cdb2ab1437034f94789e1517ad64752149a6edb7
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Tue Jul 23 15:43:41 2019 +0100
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 13:54:38 2019 +0100

    input: handle backspace on android/gboard more robustly.
    
    We get no direction hint in this case - so use cursor position.
    
    Change-Id: Ic69f075507fe619ccac84f3d5a595bf6cd413a41

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index 71567ae78..e244e2495 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -401,10 +401,12 @@ L.ClipboardContainer = L.Layer.extend({
 		}
 		if (content.length < 2) {
 			console.log('Missing terminal nodes: ' + this._deleteHint);
-			if (this._deleteHint == 'delete')
-				this._removeTextContent(0, 1);
-			else if (this._deleteHint == 'backspace')
+			if (this._deleteHint == 'backspace' ||
+			    this._textArea.selectionStart === 0)
 				this._removeTextContent(1, 0);
+			else if (this._deleteHint == 'delete' ||
+				 this._textArea.selectionStart === 1)
+				this._removeTextContent(0, 1);
 			else
 				console.log('Cant detect delete or backspace');
 			this._emptyArea();
commit 26dc90887fe44d86b83569e3d44039a9dfb2fb4e
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Mon Jul 22 20:46:46 2019 +0100
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 13:54:24 2019 +0100

    Enable firefox, IE11, Edge, etc.
    
    Ignore backspace keypress to avoid duplicate deletion in firefox.
    Handle distinguishing <space><delete> from <backspace>.
    
    Change-Id: Ia279aab929977b3522452adcbfac0c4aad189771

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index 709045824..71567ae78 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -13,11 +13,19 @@ L.ClipboardContainer = L.Layer.extend({
 		// compositionstart/compositionend events; unused
 		this._isComposing = false;
 
+		// We need to detect whether delete or backspace was
+		// pressed sometimes - consider '  foo' -> ' foo'
+		this._deleteHint = ''; // or 'delete' or 'backspace'
+
 		// Clearing the area can generate input events
 		this._ignoreInputCount = 0;
 
 		// Content
 		this._lastContent = []; // unicode characters
+		this._lastCursor = 1;   // last cursor position.
+
+		// Might need to be \xa0 in some legacy browsers ?
+		this._spaceChar = ' ';
 
 		// Debug flag, used in fancyLog(). See the debug() method.
 //		this._isDebugOn = true;
@@ -98,6 +106,7 @@ L.ClipboardContainer = L.Layer.extend({
 		onoff(this._textArea, 'compositionstart', this._onCompositionStart, this);
 		onoff(this._textArea, 'compositionupdate', this._onCompositionUpdate, this);
 		onoff(this._textArea, 'compositionend', this._onCompositionEnd, this);
+		onoff(this._textArea, 'keydown', this._onKeyDown, this);
 		onoff(this._textArea, 'keyup', this._onKeyUp, this);
 		onoff(this._textArea, 'copy cut paste', this._map._handleDOMEvent, this._map);
 
@@ -139,8 +148,6 @@ L.ClipboardContainer = L.Layer.extend({
 
 	getValue: function() {
 		var value = this._textArea.value;
-		// kill unwanted entities
-		value = value.replace(/ /g, ' ');
 		return value;
 	},
 
@@ -315,6 +322,9 @@ L.ClipboardContainer = L.Layer.extend({
 			var state = this._isComposing ? 'C' : 'N';
 			state += ' ';
 
+			var textSel = this._textArea.selectionStart + '!' + this._textArea.selectionEnd;
+			state += textSel + ' ';
+
 			var sel = window.getSelection();
 			var content = this.getValue();
 			if (sel === null)
@@ -340,6 +350,8 @@ L.ClipboardContainer = L.Layer.extend({
 					content = content.slice(0, cursorPos) + '|' + content.slice(cursorPos);
 			}
 
+			state += '[' + this._deleteHint + '] ';
+
 			console.log2(
 				+ new Date() + ' %cINPUT%c: ' + state
 				+ '"' + content + '" ' + type + '%c ',
@@ -352,7 +364,7 @@ L.ClipboardContainer = L.Layer.extend({
 	},
 
 	// Fired when text has been inputed, *during* and after composing/spellchecking
-	_onInput: function _onInput(/* ev */) {
+	_onInput: function _onInput(ev) {
 		this._map.notifyActive();
 
 		if (this._ignoreInputCount > 0) {
@@ -360,22 +372,44 @@ L.ClipboardContainer = L.Layer.extend({
 			return;
 		}
 
+		if (ev.inputType) {
+			if (ev.inputType == 'deleteContentForward')
+				this._deleteHint = 'delete';
+			else if (ev.inputType == 'deleteContentBackward')
+				this._deleteHint = 'backspace';
+			else
+				this._deleteHint = '';
+		}
+
 		var content = this.getValueAsCodePoints();
 
+		var spaceChar = this._spaceChar.charCodeAt(0);
+
 		// We use a different leading and terminal space character
 		// to differentiate backspace from delete, then replace the character.
-		if (content[0] !== 16*10) { // missing initial non-breaking space.
+		if (content.length < 1 || content[0] !== spaceChar) { // missing initial space
 			console.log('Sending backspace');
 			this._removeTextContent(1, 0);
 			this._emptyArea();
 			return;
 		}
-		if (content[content.length-1] !== 32) { // missing trailing space.
+		if (content[content.length-1] !== spaceChar) { // missing trailing space.
 			console.log('Sending delete');
 			this._removeTextContent(0, 1);
 			this._emptyArea();
 			return;
 		}
+		if (content.length < 2) {
+			console.log('Missing terminal nodes: ' + this._deleteHint);
+			if (this._deleteHint == 'delete')
+				this._removeTextContent(0, 1);
+			else if (this._deleteHint == 'backspace')
+				this._removeTextContent(1, 0);
+			else
+				console.log('Cant detect delete or backspace');
+			this._emptyArea();
+			return;
+		}
 
 		// remove leading & tailing spaces.
 		content = content.slice(1, -1);
@@ -389,18 +423,37 @@ L.ClipboardContainer = L.Layer.extend({
 			    '\tnew "' + String.fromCharCode.apply(null, content) + '" (' + content.length + ')' + '\n' +
 			    '\told "' + String.fromCharCode.apply(null, this._lastContent) + '" (' + this._lastContent.length + ')');
 
-		var remove = this._lastContent.length - matchTo;
-		if (remove > 0)
-			this._removeTextContent(remove, 0);
+		var removeBefore = this._lastContent.length - matchTo;
+		var removeAfter = 0;
+
+		if (this._lastContent.length > content.length)
+		{
+			// Pressing '<space><delete>' can delete our terminal space
+			// such that subsequent deletes will do nothing; need to
+			// detect and reset in this case.
+			if (this._deleteHint === 'delete')
+			{
+				removeBefore--;
+				removeAfter++;
+			}
+		}
+
+		if (removeBefore > 0 || removeAfter > 0)
+			this._removeTextContent(removeBefore, removeAfter);
 
 		var newText = content;
 		if (matchTo > 0)
 			newText = newText.slice(matchTo);
 
 		this._lastContent = content;
+		this._lastCursor = this._textArea.selectionStart;
 
 		if (newText.length > 0)
 			this._sendText(String.fromCharCode.apply(null, newText));
+
+		// was a 'delete' and we need to reset world.
+		if (removeAfter > 0)
+			this._emptyArea();
 	},
 
 	// Sends the given (UTF-8) string of text to lowsd, as IME (text composition)
@@ -451,15 +504,18 @@ L.ClipboardContainer = L.Layer.extend({
 		this._ignoreInputCount++;
 		// Note: 0xA0 is 160, which is the character code for non-breaking space:
 		// https://www.fileformat.info/info/unicode/char/00a0/index.htm
+
 		// Using normal spaces would make FFX/Gecko collapse them into an
 		// empty string.
+		// FIXME: is that true !? ...
 
 		console.log('Set old/lastContent to empty');
 		this._lastContent = [];
 
-		this._textArea.value = '\xa0 ';
+		this._textArea.value = this._spaceChar + this._spaceChar;
 		/// TODO: Check that this selection method works with MSIE11
 		this._textArea.setSelectionRange(1, 1);
+		this._lastCursor = 1;
 
 		this._ignoreInputCount--;
 	},
@@ -497,66 +553,13 @@ L.ClipboardContainer = L.Layer.extend({
 		this._emptyArea();
 	},
 
-	// Override the system default for pasting into the textarea/contenteditable,
-	// and paste into the document instead.
-	_onPaste: function _onPaste(ev) {
-		// Prevent the event's default - in this case, prevent the clipboard contents
-		// from being added to the hidden textarea and firing 'input'/'textInput' events.
-		ev.preventDefault();
-
-		// TODO: handle internal selection here (compare pasted plaintext with the
-		// last copied/cut plaintext, send a UNO 'paste' command over websockets if so.
-		// 		if (this._lastClipboardText === ...etc...
-
-		var pasteString;
-		if (ev.clipboardData) {
-			pasteString = ev.clipboardData.getData('text/plain'); // non-IE11
-		} else if (window.clipboardData) {
-			pasteString = window.clipboardData.getData('Text'); // IE 11
-		}
-
-		if (pasteString && pasteString === this._lastClipboardText) {
-			// If the pasted text is the same as the last copied/cut text,
-			// let lowsd use LOK's clipboard instead. This is done in order
-			// to keep formatting and non-text bits.
-			this._map._socket.sendMessage('uno .uno:Paste');
-			return;
-		}
-
-		// Let the TileLayer functionality take care of sending the
-		// DataTransfer from the event to lowsd.
-		this._map._docLayer._dataTransferToDocument(
-			ev.clipboardData || window.clipboardData /* IE11 */
-		);
-
-		this._abortComposition();
-	},
-
-	// Override the system default for cut & copy - ensure that the system clipboard
-	// receives *plain text* (instead of HTML/RTF), and save internal state.
-	// TODO: Change the 'gettextselection' command, so that it can fetch the HTML
-	// version of the copied text **maintaining typefaces**.
-	_onCutCopy: function _onCutCopy(ev) {
-		var plaintext = document.getSelection().toString();
-
-		this._lastClipboardText = plaintext;
-
-		if (ev.type === 'copy') {
-			this._map._socket.sendMessage('uno .uno:Copy');
-		} else if (ev.type === 'cut') {
-			this._map._socket.sendMessage('uno .uno:Cut');
-		}
-
-		if (event.clipboardData) {
-			event.clipboardData.setData('text/plain', plaintext); // non-IE11
-		} else if (window.clipboardData) {
-			window.clipboardData.setData('Text', plaintext); // IE 11
-		} else {
-			console.warn('Could not set the clipboard contents to plain text.');
-			return;
-		}
-
-		event.preventDefault();
+	_onKeyDown: function _onKeyDown(ev) {
+		if (ev.keyCode == 8)
+			this._deleteHint = 'backspace';
+		else if (ev.keyCode == 46)
+			this._deleteHint = 'delete';
+		else
+			this._deleteHint = '';
 	},
 
 	// Check arrow keys on 'keyup' event; using 'ArrowLeft' or 'ArrowRight'
diff --git a/loleaflet/src/map/handler/Map.Keyboard.js b/loleaflet/src/map/handler/Map.Keyboard.js
index 0ef5d1a66..1e5299ef7 100644
--- a/loleaflet/src/map/handler/Map.Keyboard.js
+++ b/loleaflet/src/map/handler/Map.Keyboard.js
@@ -338,6 +338,12 @@ L.Map.Keyboard = L.Handler.extend({
 				}
 			}
 			else if ((ev.type === 'keypress') && (!this.handleOnKeyDownKeys[keyCode] || charCode !== 0)) {
+				if (keyCode === 8 || keyCode === 46)
+				{
+					// handled generically in ClipboardContainer.js
+					console.log('Ignore backspace/delete keypress');
+					return;
+				}
 				if (charCode === keyCode && charCode !== 13) {
 					// Chrome sets keyCode = charCode for printable keys
 					// while LO requires it to be 0
commit 54251f39e7ed4380c58d9a6b9bd702d689ded0e4
Author:     Iván Sánchez Ortega <ivan.sanchez at collabora.com>
AuthorDate: Mon Jul 22 17:06:55 2019 +0100
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 13:53:04 2019 +0100

    Prevent automatic line breaks in the textarea.
    
    Change-Id: I6040f2f2e0285d6872686c7db2edfc08df4522bc

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index c18a7ebe9..709045824 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -197,6 +197,12 @@ L.ClipboardContainer = L.Layer.extend({
 		this._textArea.setAttribute('autocomplete', 'off');
 		this._textArea.setAttribute('spellcheck', 'false');
 
+		// Prevent automatic line breaks in the textarea. Without this,
+		// chromium/blink will trigger input/insertLineBreak events by
+		// just adding whitespace.
+		// See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#attr-wrap
+		this._textArea.setAttribute('wrap', 'off');
+
 		this._setupStyles();
 
 		this._emptyArea();
commit 6f9508863fcc21b50197f24f8d00d308686aa2e0
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Mon Jul 22 17:01:03 2019 +0100
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 13:52:36 2019 +0100

    input: start again - switch to using textarea and diffing it's content.
    
    A much simpler, and more robust approach.
    
    Change-Id: I855818e69845212523848464a4ab07d22a762dba

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index d89c7fa9a..c18a7ebe9 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -2,68 +2,25 @@
 /*
  * L.ClipboardContainer is the hidden textarea, which handles text
  * input events and clipboard selection.
+ *
  */
 
 /* global */
 
 L.ClipboardContainer = L.Layer.extend({
 	initialize: function() {
-		// Queued input - this shall be sent to lowsd after a short timeout,
-		// and might be canceled in the event of a 'deleteContentBackward'
-		// input event, to account for predictive keyboard behaviour.
-		this._queuedInput = '';
-		this._queueTimer = undefined;
-
-		// Flag to denote the composing state, derived from  compositionstart/compositionend events.
-		// Needed for a edge case in Chrome+AOSP where an
-		// "input/deleteContentBackward" event is fired with "isComposing" set
-		// to false even though it happens *before* a "compositionend" event.
-		// Also for some cases in desktop Safari when an InputEvent doesn't have a "isComposing"
-		// property (and therefore evaluates to "undefined")
+		// Flag to denote the composing state, derived from
+		// compositionstart/compositionend events; unused
 		this._isComposing = false;
 
-		// Stores the range(s) of the last 'beforeinput' event, so that the input event
-		// can access it.
-		this._lastRanges = [];
-
-		// Stores the data of the last 'compositionstart' event. Needed to abort
-		// composition when going back to spellcheck a word in FFX/Gecko + GBoard.
-		// 		this._lastCompositionStartData = [];
-
-		// Stores the type of the last 'input' event. Needed to abort composition
-		// when going back to spellcheck a word in FFX/Gecko + AnySoftKeyboard and
-		// some other scenarios.
-		this._lastInputType = '';
-
-		// Length of the document's selection when the last 'beforeinput' event was
-		// handled. Needed to catch and handle an edge case in Chrome where hitting
-		// either delete or backspace with active selection sends messages for both
-		// the input event and the keystrokes.
-		this._selectionLengthAtBeforeInput = 0;
-
-		// Idem to _lastInputType. Needed to handle the right keystroke on the edge case
-		// that this._selectionLengthAtBeforeInput helps catch.
-		this._lastBeforeInputType = '';
-
-		// Capability check.
-		this._hasInputType = window.InputEvent && 'inputType' in window.InputEvent.prototype;
-
-		// The "normal" order of composition events is:
-		// - compositionstart
-		// - compositionupdate
-		// - input/insertCompositionText
-		// But if the user goes back to a previous word for spellchecking, the browser
-		// might fire a compositionupdate *without* a corresponding input event later.
-		// In that case, the composition has to be aborted. Because of the order of
-		// the events, a timer is needed to check for the right conditions.
-		this._abortCompositionTimeout = undefined;
-
-		// Defines whether to use a <input type=textarea> (when true) or a
-		// <div contenteditable> (when false)
-		this._legacyArea = L.Browser.safari;
+		// Clearing the area can generate input events
+		this._ignoreInputCount = 0;
+
+		// Content
+		this._lastContent = []; // unicode characters
 
 		// Debug flag, used in fancyLog(). See the debug() method.
-// 		this._isDebugOn = true;
+//		this._isDebugOn = true;
 		this._isDebugOn = false;
 
 		this._initLayout();
@@ -77,13 +34,8 @@ L.ClipboardContainer = L.Layer.extend({
 			draggable: true
 		}).on('dragend', this._onCursorHandlerDragEnd, this);
 
-		// Used for internal cut/copy/paste in the same document - to tell
-		// lowsd whether to use its internal clipboard state (rich text) or to send
-		// the browser contents (plaintext)
-		this._lastClipboardText = undefined;
-
-		// This variable prevents from hiding the keyboard just before focus call
-		this.dontBlur = false;
+		var that = this;
+		this._selectionHandler = function(ev) { that._onEvent(ev); }
 	},
 
 	onAdd: function() {
@@ -104,9 +56,6 @@ L.ClipboardContainer = L.Layer.extend({
 		}
 
 		L.DomEvent.on(this._map.getContainer(), 'mousedown touchstart', this._abortComposition, this);
-		// 		L.DomEvent.on(this._map.getContainer(), 'mousedown touchstart', function(ev) {
-		// 			this._fancyLog(ev.type);
-		// 		}, this);
 	},
 
 	onRemove: function() {
@@ -121,7 +70,7 @@ L.ClipboardContainer = L.Layer.extend({
 	},
 
 	_onFocusBlur: function(ev) {
-// 		console.log(ev.type, performance.now(), ev);
+		this._fancyLog(ev.type, '');
 
 		if (this.dontBlur && ev.type == 'blur') {
 			this._map.focus();
@@ -137,46 +86,25 @@ L.ClipboardContainer = L.Layer.extend({
 
 		var onoff = (ev.type == 'focus' ? L.DomEvent.on : L.DomEvent.off).bind(L.DomEvent);
 
-		onoff(this._textArea, 'compositionstart', this._onCompositionStart, this);
-		onoff(this._textArea, 'compositionend', this._onCompositionEnd, this);
-		onoff(this._textArea, 'beforeinput', this._onBeforeInput, this);
-		onoff(this._textArea, 'cut copy', this._onCutCopy, this);
-		onoff(this._textArea, 'paste', this._onPaste, this);
-		onoff(this._textArea, 'input', this._onInput, this);
-		onoff(this._textArea, 'keyup', this._onKeyUp, this);
-
-		if (L.Browser.ie) {
-			onoff(this._textArea, 'textinput', this._onMSIETextInput, this);
-			onoff(this._textArea, 'keydown', this._onMSIEKeyDown, this);
-		}
-		if (L.Browser.edge) {
-			onoff(this._textArea, 'keydown', this._onEdgeKeyDown, this);
-		}
-
-		// Stock android browsers (using an embedded WebView) wihout an InputEvent
-		// implementation behave similar to MSIE in regards to "enter" & "delete"
-		// keypresses
-		if (L.Browser.android && L.Browser.mobileWebkit3d && !('InputEvent' in window)) {
-			onoff(this._textArea, 'keydown', this._onMSIEKeyDown, this);
-		}
-
-		// Debug
+		// Debug - connect first for saner logging.
 		onoff(
 			this._textArea,
-			'copy cut compositionstart compositionupdate compositionend select selectionstart selectionchange keydown keypress keyup beforeinput textInput textinput input',
+			'copy cut compositionstart compositionupdate compositionend select keydown keypress keyup beforeinput textInput textinput input',
 			this._onEvent,
 			this
 		);
 
+		onoff(this._textArea, 'input', this._onInput, this);
+		onoff(this._textArea, 'compositionstart', this._onCompositionStart, this);
+		onoff(this._textArea, 'compositionupdate', this._onCompositionUpdate, this);
+		onoff(this._textArea, 'compositionend', this._onCompositionEnd, this);
+		onoff(this._textArea, 'keyup', this._onKeyUp, this);
+		onoff(this._textArea, 'copy cut paste', this._map._handleDOMEvent, this._map);
+
 		this._map.notifyActive();
 
-		if (ev.type === 'blur') {
-			if (this._isComposing) {
-				this._queueInput(this._compositionText);
-			}
-			this._abortComposition();
-		} else {
-			this._winId = 0;
+		if (ev.type === 'blur' && this._isComposing) {
+			this._abortComposition(ev);
 		}
 	},
 
@@ -196,16 +124,7 @@ L.ClipboardContainer = L.Layer.extend({
 	// Marks the content of the textarea/contenteditable as selected,
 	// for system clipboard interaction.
 	select: function select() {
-		if (this._legacyArea) {
-			this._textArea.select();
-		} else {
-			// As per https://stackoverflow.com/a/6150060/4768502
-			var range = document.createRange();
-			range.selectNodeContents(this._textArea);
-			var sel = window.getSelection();
-			sel.removeAllRanges();
-			sel.addRange(range);
-		}
+		this._textArea.select();
 	},
 
 	warnCopyPaste: function() {
@@ -219,11 +138,31 @@ L.ClipboardContainer = L.Layer.extend({
 	},
 
 	getValue: function() {
-		if (this._legacyArea) {
-			return this._textArea.value;
-		} else {
-			return this._textArea.textContent;
+		var value = this._textArea.value;
+		// kill unwanted entities
+		value = value.replace(/ /g, ' ');
+		return value;
+	},
+
+	getValueAsCodePoints: function() {
+		var value = this.getValue();
+		var arr = [];
+		var code;
+		for (var i = 0; i < value.length; ++i)
+		{
+			code = value.charCodeAt(i);
+
+			// if it were not for IE11: "for (code of value)" does the job.
+			if (code >= 0xd800 && code <= 0xdbff) // handle UTF16 pairs.
+			{
+				// TESTME: harder ...
+				var high = (code - 0xd800) << 10;
+				code = value.charCodeAt(++i);
+				code = high + code - 0xdc00 + 0x100000;
+			}
+			arr.push(code);
 		}
+		return arr;
 	},
 
 	setValue: function(val) {
@@ -251,18 +190,7 @@ L.ClipboardContainer = L.Layer.extend({
 		// The textarea allows the keyboard to pop up and so on.
 		// Note that the contents of the textarea are NOT deleted on each composed
 		// word, in order to make
-
-		if (this._legacyArea) {
-			// Force a textarea on Safari. This is two-fold: Safari doesn't fire
-			// input/insertParagraph events on an empty&focused contenteditable,
-			// but does fire input/insertLineBreak on an empty&focused textarea;
-			// Safari on iPad would show bold/italic/underline native controls
-			// which cannot be handled with the current implementation.
-			this._textArea = L.DomUtil.create('textarea', 'clipboard', this._container);
-		} else {
-			this._textArea = L.DomUtil.create('div', 'clipboard', this._container);
-			this._textArea.setAttribute('contenteditable', 'true');
-		}
+		this._textArea = L.DomUtil.create('textarea', 'clipboard', this._container);
 		this._textArea.setAttribute('autocapitalize', 'off');
 		this._textArea.setAttribute('autofocus', 'true');
 		this._textArea.setAttribute('autocorrect', 'off');
@@ -270,6 +198,8 @@ L.ClipboardContainer = L.Layer.extend({
 		this._textArea.setAttribute('spellcheck', 'false');
 
 		this._setupStyles();
+
+		this._emptyArea();
 	},
 
 	_setupStyles: function() {
@@ -346,42 +276,21 @@ L.ClipboardContainer = L.Layer.extend({
 		L.DomUtil.setPosition(this._container, pos);
 	},
 
-	// Return the content of _lastRanges as a string.
-	_lastRangesString: function() {
-		if (
-			this._lastRanges[0] &&
-			'startOffset' in this._lastRanges[0] &&
-			'endOffset' in this._lastRanges[0]
-		) {
-			return this._lastRanges[0].startOffset + '-' + this._lastRanges[0].endOffset;
-		}
-
-		return undefined;
-	},
-
 	// Generic handle attached to most text area events, just for debugging purposes.
 	_onEvent: function _onEvent(ev) {
 		var msg = {
-			type: ev.type,
 			inputType: ev.inputType,
 			data: ev.data,
 			key: ev.key,
 			isComposing: ev.isComposing
 		};
 
-		msg.lastRanges = this._lastRangesString();
-
-		if (ev.type === 'input') {
-			msg.inputType = ev.inputType;
-		}
-
 		if ('key' in ev) {
 			msg.key = ev.key;
 			msg.keyCode = ev.keyCode;
 			msg.code = ev.code;
 			msg.which = ev.which;
 		}
-
 		this._fancyLog(ev.type, msg);
 	},
 
@@ -397,8 +306,37 @@ L.ClipboardContainer = L.Layer.extend({
 
 		// Pretty-print on console (but only if "tile layer debug mode" is active)
 		if (this._isDebugOn) {
+			var state = this._isComposing ? 'C' : 'N';
+			state += ' ';
+
+			var sel = window.getSelection();
+			var content = this.getValue();
+			if (sel === null)
+				state += '-1';
+			else
+			{
+				state += sel.rangeCount;
+
+				state += ' ';
+				var cursorPos = -1;
+				for (var i = 0; i < sel.rangeCount; ++i)
+				{
+					var range = sel.getRangeAt(i);
+					state += range.startOffset + '-' + range.endOffset + ' ';
+					if (cursorPos < 0)
+						cursorPos = range.startOffset;
+				}
+				if (sel.toString() !== '')
+					state += ': "' + sel.toString() + '" ';
+
+				// inject probable cursor
+				if (cursorPos >= 0)
+					content = content.slice(0, cursorPos) + '|' + content.slice(cursorPos);
+			}
+
 			console.log2(
-				+new Date() + ' %cINPUT%c: ' + type + '%c',
+				+ new Date() + ' %cINPUT%c: ' + state
+				+ '"' + content + '" ' + type + '%c ',
 				'background:#bfb;color:black',
 				'color:green',
 				'color:black',
@@ -408,153 +346,55 @@ L.ClipboardContainer = L.Layer.extend({
 	},
 
 	// Fired when text has been inputed, *during* and after composing/spellchecking
-	_onInput: function _onInput(ev) {
+	_onInput: function _onInput(/* ev */) {
 		this._map.notifyActive();
 
-		var previousInputType = this._lastInputType;
-		this._lastInputType = ev.inputType;
-
-		if (!('inputType' in ev)) {
-			// Legacy MSIE or Android WebView, just send the contents of the
-			// container and clear it.
-			if (this._isComposing) {
-				this._sendCompositionEvent('input', this._textArea.textContent);
-			} else {
-				if (
-					this._textArea.textContent.length === 0 &&
-					this._textArea.innerHTML.indexOf('<br>') !== -1
-				) {
-					// WebView-specific hack: when the user presses enter, textContent
-					// is empty instead of "\n", but a <br> is added to the
-					// contenteditable.
-					this._sendText('\n');
-				} else {
-					this._sendText(this._textArea.textContent);
-				}
-				this._emptyArea();
-			}
-		} else if (ev.inputType === 'insertCompositionText') {
-			// The text being composed has changed.
-			// This is diferent from a 'compositionupdate' event: a 'compositionupdate'
-			// event might be fired when going back to spellcheck a word, but an
-			// 'input/insertCompositionText' happens only when the user is adding to a
-			// composition.
-
-			// Abort composition when going back for spellchecking, FFX/Gecko
-			if (L.Browser.gecko && previousInputType === 'deleteContentBackward') {
-				return;
-			}
-
-			clearTimeout(this._abortCompositionTimeout);
-
-			if (!this._isComposing) {
-				// FFX/Gecko: Regardless of on-screen keyboard, there is a
-				// input/insertCompositionText with isComposing=false *after*
-				// the compositionend event.
-				this._queueInput(ev.data);
-			} else {
-				// Flush the queue
-				if (this._queuedInput !== '') {
-					this._sendQueued();
-				}
+		if (this._ignoreInputCount > 0) {
+			console.log('ignoring synthetic input ' + this._ignoreInputCount);
+			return;
+		}
 
-				// Tell lowsd about the current text being composed
-				this._sendCompositionEvent('input', ev.data);
-			}
-		} else if (ev.inputType === 'insertText') {
-			// Non-composed text has been added to the text area.
-
-			// FFX+AOSP / FFX+AnySoftKeyboard edge case: Autocompleting a
-			// one-letter word will fire a input/insertText with that word
-			// right after a compositionend + input/insertCompositionText.
-			// In that case, ignore the
-			if (
-				L.Browser.gecko &&
-				ev.data.length === 1 &&
-				previousInputType === 'insertCompositionText' &&
-				ev.data === this._queuedInput
-			) {
-				return;
-			}
+		var content = this.getValueAsCodePoints();
 
-			if (!this._isComposing) {
-				this._queueInput(ev.data);
-			}
-		} else if (ev.inputType === 'insertParagraph') {
-			// Happens on non-Safari on the contenteditable div.
-			this._queueInput('\n');
+		// We use a different leading and terminal space character
+		// to differentiate backspace from delete, then replace the character.
+		if (content[0] !== 16*10) { // missing initial non-breaking space.
+			console.log('Sending backspace');
+			this._removeTextContent(1, 0);
 			this._emptyArea();
-		} else if (ev.inputType === 'insertLineBreak') {
-			// Happens on Safari on the textarea.
-			this._queueInput('\n');
+			return;
+		}
+		if (content[content.length-1] !== 32) { // missing trailing space.
+			console.log('Sending delete');
+			this._removeTextContent(0, 1);
 			this._emptyArea();
-		} else if (ev.inputType === 'deleteContentBackward') {
-			if (this._isComposing) {
-				// deletion refers to the text being composed, noop
-				return;
-			}
+			return;
+		}
 
-			// Delete text backwards - as many characters as indicated in the previous
-			// 'beforeinput' event
+		// remove leading & tailing spaces.
+		content = content.slice(1, -1);
 
-			// These are sent e.g. by the GBoard keyboard when autocorrecting, meaning
-			// "I'm about to send another textInput event with the right word".
+		var matchTo = 0;
+		var sharedLength = Math.min(content.length, this._lastContent.length);
+		while (matchTo < sharedLength && content[matchTo] === this._lastContent[matchTo])
+			matchTo++;
 
-			var count = 1;
-			if (this._lastRanges[0]) {
-				count = this._lastRanges[0].endOffset - this._lastRanges[0].startOffset;
-			}
+		console.log('Comparison matchAt ' + matchTo + '\n' +
+			    '\tnew "' + String.fromCharCode.apply(null, content) + '" (' + content.length + ')' + '\n' +
+			    '\told "' + String.fromCharCode.apply(null, this._lastContent) + '" (' + this._lastContent.length + ')');
 
-			// If there is queued input, cancel that first. This prevents race conditions
-			// in lowsd (compose-backspace-compose messages are handled as
-			// compose-compose-backspace).
-			// Deleting queued input happens when accepting an autocorrect suggestion;
-			// emptying the area in that case would break text composition workflow.
-			var l = this._queuedInput.length;
-			if (l >= count) {
-				this._queuedInput = this._queuedInput.substring(0, l - count);
-			} else {
-				this._removeTextContext(count, 0);
-				this._emptyArea();
-			}
+		var remove = this._lastContent.length - matchTo;
+		if (remove > 0)
+			this._removeTextContent(remove, 0);
 
-			L.DomEvent.stop(ev);
-		} else if (ev.inputType === 'deleteContentForward') {
-			// Send a UNO 'delete' keystroke
-			this._sendKeyEvent(46, 1286);
-			this._emptyArea();
-		} else if (ev.inputType === 'insertReplacementText') {
-			// Happens only in Safari (both iOS and OS X) with autocorrect/spellcheck
-			// FIXME: It doesn't provide any info about how much to replace!
-			// This is currently disabled by means of using a <input type=textarea
-			// autocorrect=off> in Safari.
-			/// TODO: Send a specific message to lowsd to find the last word and
-			/// replace it with the given one.
-		} else if (ev.inputType === 'deleteCompositionText') {
-			// Safari on OS X is extra nice about composition - it notifies the
-			// browser whenever the composition text should be deleted.
-		} else if (ev.inputType === 'insertFromComposition') {
-			// Observed only on desktop Safari just before a "compositionend"
-			// TODO: Check if the
-			this._queueInput(ev.data);
-		} else if (ev.inputType === 'deleteByCut') {
-			// Called when Ctrl+X'ing
-			this._abortComposition(ev);
-		} else {
-			console.error('Unhandled type of input event!!', ev.inputType, ev);
-			throw new Error('Unhandled type of input event!');
-		}
-	},
+		var newText = content;
+		if (matchTo > 0)
+			newText = newText.slice(matchTo);
+
+		this._lastContent = content;
 
-	// Chrome and MSIE (from 9 all the way up to Edge) send the non-standard
-	// "textInput" DOM event.
-	// In Chrome, this is fired *just before* the compositionend event, and *before*
-	// any other "input" events which would add text to the area (e.g. "insertText")
-	// "textInput" events are used in MSIE, since the "input" events do not hold
-	// information about the text added to the area.
-	// In MSIE11, the event is "textinput" (all lowercase).
-	_onMSIETextInput: function _onInput(ev) {
-		this._queueInput(ev.data);
+		if (newText.length > 0)
+			this._sendText(String.fromCharCode.apply(null, newText));
 	},
 
 	// Sends the given (UTF-8) string of text to lowsd, as IME (text composition)
@@ -565,10 +405,14 @@ L.ClipboardContainer = L.Layer.extend({
 		// MSIE/Edge cannot compare a string to "\n" for whatever reason,
 		// so compare charcode as well
 		if (text === '\n' || (text.length === 1 && text.charCodeAt(0) === 13)) {
-			// The composition messages doesn't play well with just a line break,
-			// therefore send a keystroke.
-			this._sendKeyEvent(13, 1280);
-			this._emptyArea();
+			// we get a duplicate key-event on Gecko, oddly so drop it.
+			if (!L.Browser.gecko)
+			{
+				// The composition messages doesn't play well with just a line break,
+				// therefore send a keystroke.
+				this._sendKeyEvent(13, 1280);
+				this._emptyArea();
+			}
 		} else {
 			// The composition messages doesn't play well with line breaks inside
 			// the composed word (e.g. word and a newline are queued client-side
@@ -596,161 +440,34 @@ L.ClipboardContainer = L.Layer.extend({
 	// (some combination of browser + input method don't fire those on an
 	// empty contenteditable).
 	_emptyArea: function _emptyArea() {
-		if (this._hasInputType) {
-			// Note: 0xA0 is 160, which is the character code for non-breaking space:
-			// https://www.fileformat.info/info/unicode/char/00a0/index.htm
-			// Using normal spaces would make FFX/Gecko collapse them into an
-			// empty string.
-			if (this._legacyArea) {
-				this._textArea.value = '\xa0\xa0';
-				/// TODO: Check that this selection method works with MSIE11
-				///
-				this._textArea.setSelectionRange(1, 1);
-			} else {
-				// The strategy for a contenteditable is to add a space, select it,
-				// collapse the selection, then add two text nodes after and before
-				// the text node with the selection but with a delay of one frame.
-				// The frame delay is done in order to avoid the AOSP on-screen keyboard
-				// from moving the cursor caret around. On delete/backspace, AOSP
-				// keyboard would somehow ignore the selection ranges and move the caret
-				// before/after the empty spaces.
-
-				/// FIXME: The aforementioned strategy makes Android + GBoard + Firefox fail:
-				/// trying to press the "ArrowLeft" or "ArrowUp" keys in the
-				this._textArea.innerText = '\xa0';
-
-				var range = document.createRange();
-				range.selectNodeContents(this._textArea);
-				var sel = window.getSelection();
-				sel.removeAllRanges();
-				sel.addRange(range);
-				sel.collapse(this._textArea.childNodes[0]);
-
-				L.Util.requestAnimFrame(function() {
-					this._textArea.prepend('\xa0');
-					this._textArea.append('\xa0');
-				}.bind(this));
+		this._fancyLog('empty-area');
 
-			}
-		} else if (this._legacyArea) {
-			this._textArea.value = '';
-		} else {
-			// In order to empty a contenteditable when the two-spaces-hack is not
-			// in place, access its first text node child and empty it.
-			if (this._textArea.childNodes.length === 1) {
-				this._textArea.childNodes[0].data = '';
-			} else if (this._textArea.childNodes.length > 1) {
-				this._textArea.innerText = '';
-				this._textArea.innerHTML = '';
-				// Sanity check, should never be reached.
-				// True - but for now - lets not kill the world in this case ...
-//				throw new Error('Unexpected: more than one text node inside the contenteditable.');
-			}
-		}
-	},
+		this._ignoreInputCount++;
+		// Note: 0xA0 is 160, which is the character code for non-breaking space:
+		// https://www.fileformat.info/info/unicode/char/00a0/index.htm
+		// Using normal spaces would make FFX/Gecko collapse them into an
+		// empty string.
 
-	// The getTargetRanges() method usually returns an empty array,
-	// since the ranges are only valid at the "beforeinput" stage.
-	// Fetching this info for later is important, especially
-	// for Chrome+"input/deleteContentBackward" events.
-	// Also, some deleteContentBackward/Forward input types
-	// only happen at 'beforeinput' and not at 'input' events,
-	// particularly when the textarea/contenteditable is empty, but
-	// only in some configurations.
-	_onBeforeInput: function _onBeforeInput(ev) {
-		this._lastRanges = ev.getTargetRanges();
-		// 		console.log('onBeforeInput range: ', ev.inputType, ranges,
-		// 					ranges[0] && ranges[0].startOffset,
-		// 					ranges[0] && ranges[0].endOffset);
-
-		this._lastBeforeInputType = ev.inputType;
-		this._selectionLengthAtBeforeInput = selection.length;
-
-		this._fancyLog('beforeinput selection', window.getSelection().toString());
-		this._fancyLog('beforeinput range', this._lastRangesString());
-
-		// FIXME: a hack - this assumes that nothing changed / no auto-correction inside the Kit.
-		// FIXME: only mobile for now to reduce risk ...
-		if (window.mode.isMobile() || window.mode.isTablet())
-		{
-			var seltext = window.getSelection();
-			if (!this._isComposing && seltext && seltext.toString() && seltext.toString().length > 0)
-			{
-				var len = seltext.toString().length;
-				this._fancyLog('selection overtype', seltext.toString() + ' len ' + len + ' chars "' + this._queuedInput + '"');
-				if (this._queuedInput)
-				{
-					var size = this._queuedInput.length;
-					var redux = Math.min(size, len);
-					this._queuedInput = this._queuedInput.slice(0,-redux);
-					len -= redux;
-					console.log2('queue overtype', 'removed ' + redux + ' from queued input to "' + this._queuedInput + '"');
-				}
-				// FIXME: this is the more hacky bit - if the Kit changed under us.
-				for (var i = 0; i < len; ++i)
-					this._sendKeyEvent(8, 1283);
-			}
-		}
+		console.log('Set old/lastContent to empty');
+		this._lastContent = [];
 
-		// When trying to delete (i.e. backspace) on an empty textarea, the input event
-		// won't be fired afterwards. Handle backspace here instead.
-
-		// Chrome + AOSP does *not* send any "beforeinput" events when the
-		// textarea is empty. In that case, a 'keydown'+'keypress'+'keyup' sequence
-		// for charCode=8 is fired, and handled by the Map.Keyboard.js.
-		if ((this._winId === 0 && this._textArea.textContent.length === 0) ||
-			ev.findMyTextContentAre.length == 0) {
-			if (ev.inputType === 'deleteContentBackward') {
-				this._sendKeyEvent(8, 1283);
-			} else if (ev.inputType === 'deleteContentForward') {
-				this._sendKeyEvent(46, 1286);
-			}
-		}
-	},
+		this._textArea.value = '\xa0 ';
+		/// TODO: Check that this selection method works with MSIE11
+		this._textArea.setSelectionRange(1, 1);
 
-	_queueInput: function _queueInput(text) {
-		this._map.notifyActive();
-
-		if (text === null) {
-			// Chrome sends a input/insertText with 'null' event data when
-			// typing a newline quickly after typing text.
-			console.warn('Tried to queue null text! Maybe a lost newline?');
-			this._queuedInput += '\n';
-			clearTimeout(this._queueTimer);
-		}
-		else if (this._queuedInput !== '') {
-			console.warn(
-				'Text input already queued - recieving composition end events too fast!'
-			);
-			this._queuedInput += text;
-			clearTimeout(this._queueTimer);
-		} else {
-			this._queuedInput = text;
-		}
-
-		//console.log('_queueInput', text, ' queue is now:', {text: this._queuedInput});
-		this._queueTimer = setTimeout(this._sendQueued.bind(this), 50);
-	},
-
-	_clearQueued: function _clearQueued() {
-		// console.log('Cleared queued:', { text: this._queuedInput });
-		clearTimeout(this._queueTimer);
-		this._queuedInput = '';
-	},
-
-	_sendQueued: function _sendQueued() {
-		// console.log('Sending to lowsd (queued): ', {text: this._queuedInput});
-		this._sendText(this._queuedInput);
-		this._clearQueued();
+		this._ignoreInputCount--;
 	},
 
 	_onCompositionStart: function _onCompositionStart(/*ev*/) {
 		this._isComposing = true;
 	},
 
-	// 	_onCompositionUpdate: function _onCompositionUpdate(ev) {
-	// 		// Noop - handled at input/insertCompositionText instead.
-	// 	},
+	// Handled only in legacy situations ('input' events with an inputType
+	// property are preferred).
+	_onCompositionUpdate: function _onCompositionUpdate(ev) {
+		this._map.notifyActive();
+		this._onInput(ev);
+	},
 
 	// Chrome doesn't fire any "input/insertCompositionText" with "isComposing" set to false.
 	// Instead , it fires non-standard "textInput" events, but those can be tricky
@@ -758,37 +475,9 @@ L.ClipboardContainer = L.Layer.extend({
 	// The approach here is to use "compositionend" events *only in Chrome* to mark
 	// the composing text as committed to the text area.
 	_onCompositionEnd: function _onCompositionEnd(ev) {
-		// Check for standard chrome, and check heuristically for embedded Android
-		// WebView (without chrome user-agent string)
-		if (L.Browser.chrome || (L.Browser.android && L.Browser.webkit3d && !L.Browser.webkit)) {
-			if (this._lastInputType === 'insertCompositionText') {
-// 				console.log('Queuing input because android webview');
-				this._queueInput(ev.data);
-			} else {
-				// Ended a composition without user input, abort.
-				// This happens on Chrome+GBoard when autocompleting a word
-				// then entering a punctuation mark.
-				this._abortComposition(ev);
-			}
-		}
-
-		// Check for Safari; it fires composition events on typing diacritics with dead keys.
-		if (L.Browser.Safari) {
-			if (this._lastInputType === 'insertFromComposition') {
-				this._queueInput(ev.data);
-			} else {
-				this._abortComposition(ev);
-			}
-		}
-
-		// Tell lowsd to exit composition mode when the composition is empty
-		// This happens when deleting the whole word being composed, e.g.
-		// swipe a word then press backspace.
-		if (ev.data === '') {
-			this._sendCompositionEvent('input', '');
-		}
-
+		this._map.notifyActive();
 		this._isComposing = false;
+		this._onInput(ev);
 	},
 
 	// Called when the user goes back to a word to spellcheck or replace it,
@@ -797,11 +486,8 @@ L.ClipboardContainer = L.Layer.extend({
 	// empty the text area.
 	_abortComposition: function _abortComposition(ev) {
 		this._fancyLog('abort-composition', ev.type);
-		if (this._isComposing) {
-			this._sendCompositionEvent('input', '');
-			this._sendCompositionEvent('end', '');
+		if (this._isComposing)
 			this._isComposing = false;
-		}
 		this._emptyArea();
 	},
 
@@ -883,33 +569,14 @@ L.ClipboardContainer = L.Layer.extend({
 		}
 	},
 
-	// MSIE11 doesn't send any "textinput" events on enter, delete or backspace.
-	// (Idem for old-ish stock android browsers which do not implement InputEvents)
-	// To handle those, an event handler is added to the "keydown" event (which repeats)
-	_onMSIEKeyDown: function _onMSIEKeyDown(ev) {
-		if (!ev.shiftKey && !ev.ctrlKey && !ev.altKey && !ev.metaKey) {
-			if (ev.key === 'Delete' || ev.key === 'Del') {
-				this._sendKeyEvent(46, 1286);
-				this._emptyArea();
-			} else if (ev.key === 'Backspace') {
-				this._sendKeyEvent(8, 1283);
-				this._emptyArea();
-			} else if (ev.key === 'Enter') {
-				this._queueInput('\n');
-				this._emptyArea();
-			}
-		}
-	},
-
-	// Edge18 doesn't send any "input" events on delete or backspace
-	// To handle those, an event handler is added to the "keydown" event (which repeats)
-	_onEdgeKeyDown: function _onEdgeKeyDown(ev) {
-		// FIXME: we need enter too - share with above method ?
-		this._onMSIEKeyDown(ev);
-	},
+	// Used in the deleteContentBackward for deleting multiple characters with a single
+	// message.
+	// Will remove characters from the queue first, if there are any.
+	_removeTextContent: function _removeTextContent(before, after) {
+		console.log('Remove ' + before + ' before, and ' + after + ' after');
 
-	// Used in the deleteContentBackward for deleting multiple characters with a single message.
-	_removeTextContext: function _removeTextContext(before, after) {
+		/// TODO: rename the event to 'removetextcontent' as soon as lowsd supports it
+		/// TODO: Ask Marco about it
 		this._map._socket.sendMessage(
 			'removetextcontext id=' +
 			this._map.getWinId() +
@@ -921,6 +588,7 @@ L.ClipboardContainer = L.Layer.extend({
 	// Tiny helper - encapsulates sending a 'textinput' websocket message.
 	// "type" is either "input" for updates or "end" for commits.
 	_sendCompositionEvent: function _sendCompositionEvent(type, text) {
+		console.log('sending to lowsd: ', type, text);
 		this._map._socket.sendMessage(
 			'textinput id=' +
 				this._map.getWinId() +
@@ -932,16 +600,22 @@ L.ClipboardContainer = L.Layer.extend({
 	},
 
 	// Tiny helper - encapsulates sending a 'key' or 'windowkey' websocket message
-	_sendKeyEvent: function _sendKeyEvent(charCode, unoKeyCode) {
+	// "type" can be "input" (default) or "up"
+	_sendKeyEvent: function _sendKeyEvent(charCode, unoKeyCode, type) {
+		if (!type) {
+			type = 'input';
+		}
 		if (this._map.getWinId() === 0) {
 			this._map._socket.sendMessage(
-				'key type=input char=' + charCode + ' key=' + unoKeyCode + '\n'
+				'key type=' + type + ' char=' + charCode + ' key=' + unoKeyCode + '\n'
 			);
 		} else {
 			this._map._socket.sendMessage(
 				'windowkey id=' +
 					this._map.getWinId() +
-					' type=input char=' +
+					' type=' +
+					type +
+					' char=' +
 					charCode +
 					' key=' +
 					unoKeyCode +
commit 0e86094f1c6d6f50d945bcee9908ecd496a2c594
Author:     Iván Sánchez Ortega <ivan.sanchez at collabora.com>
AuthorDate: Thu Jul 4 15:53:23 2019 +0200
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 13:47:19 2019 +0100

    loleaflet: Unconditionally apply autocapitalize=false to ClipboardContainer
    
    Change-Id: Ib875caac26920a3ed18a7ca53fbba587cebc89b5

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index 68e27d4d8..d89c7fa9a 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -259,16 +259,15 @@ L.ClipboardContainer = L.Layer.extend({
 			// Safari on iPad would show bold/italic/underline native controls
 			// which cannot be handled with the current implementation.
 			this._textArea = L.DomUtil.create('textarea', 'clipboard', this._container);
-			this._textArea.setAttribute('autocorrect', 'off');
-			this._textArea.setAttribute('autocapitalize', 'off');
-			this._textArea.setAttribute('autocomplete', 'off');
-			this._textArea.setAttribute('spellcheck', 'false');
-			this._textArea.setAttribute('autofocus', 'true');
 		} else {
 			this._textArea = L.DomUtil.create('div', 'clipboard', this._container);
 			this._textArea.setAttribute('contenteditable', 'true');
-			this._textArea.setAttribute('autofocus', 'true');
 		}
+		this._textArea.setAttribute('autocapitalize', 'off');
+		this._textArea.setAttribute('autofocus', 'true');
+		this._textArea.setAttribute('autocorrect', 'off');
+		this._textArea.setAttribute('autocomplete', 'off');
+		this._textArea.setAttribute('spellcheck', 'false');
 
 		this._setupStyles();
 	},
commit 17c90f8910f5418af2a81665bb3546aca1c101fb
Author:     Marco Cecchetti <mrcekets at gmail.com>
AuthorDate: Wed Jul 10 10:29:50 2019 +0200
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 13:46:10 2019 +0100

    single msg for deleting multiple characters
    
    Change-Id: I589dbc933e4450d5dbcf35e99b1a55598d3dee76

diff --git a/bundled/include/LibreOfficeKit/LibreOfficeKit.h b/bundled/include/LibreOfficeKit/LibreOfficeKit.h
index 93b430f5d..6060b015b 100644
--- a/bundled/include/LibreOfficeKit/LibreOfficeKit.h
+++ b/bundled/include/LibreOfficeKit/LibreOfficeKit.h
@@ -408,6 +408,12 @@ struct _LibreOfficeKitDocumentClass
     /// @see lok::Document::getSelectionType
     int (*getSelectionType) (LibreOfficeKitDocument* pThis);
 
+    /// @see lok::Document::removeTextContext
+    void (*removeTextContext) (LibreOfficeKitDocument* pThis,
+                               unsigned nWindowId,
+                               int nBefore,
+                               int nAfter);
+
 #endif // defined LOK_USE_UNSTABLE_API || defined LIBO_INTERNAL_ONLY
 };
 
diff --git a/bundled/include/LibreOfficeKit/LibreOfficeKit.hxx b/bundled/include/LibreOfficeKit/LibreOfficeKit.hxx
index 517a38a50..a695c4113 100644
--- a/bundled/include/LibreOfficeKit/LibreOfficeKit.hxx
+++ b/bundled/include/LibreOfficeKit/LibreOfficeKit.hxx
@@ -728,6 +728,19 @@ public:
         return mpDoc->pClass->resizeWindow(mpDoc, nWindowId, width, height);
     }
 
+    /**
+     * For deleting many characters all at once
+     *
+     * @param nWindowId Specify the window id to post the input event to. If
+     * nWindow is 0, the event is posted into the document
+     * @param nBefore The characters to be deleted before the cursor position
+     * @param nAfter The characters to be deleted after the cursor position
+     */
+    void removeTextContext(unsigned nWindowId, int nBefore, int nAfter)
+    {
+        mpDoc->pClass->removeTextContext(mpDoc, nWindowId, nBefore, nAfter);
+    }
+
 #endif // defined LOK_USE_UNSTABLE_API || defined LIBO_INTERNAL_ONLY
 };
 
diff --git a/kit/ChildSession.cpp b/kit/ChildSession.cpp
index 2880e73d0..5dce1f5db 100644
--- a/kit/ChildSession.cpp
+++ b/kit/ChildSession.cpp
@@ -294,7 +294,8 @@ bool ChildSession::_handleInput(const char *buffer, int length)
                tokens[0] == "signdocument" ||
                tokens[0] == "uploadsigneddocument" ||
                tokens[0] == "exportsignanduploaddocument" ||
-               tokens[0] == "rendershapeselection");
+               tokens[0] == "rendershapeselection" ||
+               tokens[0] == "removetextcontext");
 
         if (tokens[0] == "clientzoom")
         {
@@ -414,6 +415,10 @@ bool ChildSession::_handleInput(const char *buffer, int length)
         {
             return renderShapeSelection(buffer, length, tokens);
         }
+        else if (tokens[0] == "removetextcontext")
+        {
+            return removeTextContext(buffer, length, tokens);
+        }
         else
         {
             assert(false && "Unknown command token.");
@@ -2094,6 +2099,27 @@ bool ChildSession::renderShapeSelection(const char* /*buffer*/, int /*length*/,
     return true;
 }
 
+bool ChildSession::removeTextContext(const char* /*buffer*/, int /*length*/,
+                                     const std::vector<std::string>& tokens)
+{
+    int id, before, after;
+    std::string text;
+    if (tokens.size() < 4 ||
+        !getTokenInteger(tokens[1], "id", id) || id < 0 ||
+        !getTokenInteger(tokens[2], "before", before) ||
+        !getTokenInteger(tokens[3], "after", after))
+    {
+        sendTextFrame("error: cmd=" + std::string(tokens[0]) + " kind=syntax");
+        return false;
+    }
+
+    std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex());
+    getLOKitDocument()->setView(_viewId);
+    getLOKitDocument()->removeTextContext(id, before, after);
+
+    return true;
+}
+
 /* If the user is inactive we have to remember important events so that when
  * the user becomes active again, we can replay the events.
  */
diff --git a/kit/ChildSession.hpp b/kit/ChildSession.hpp
index 3e32f311d..88166a0c4 100644
--- a/kit/ChildSession.hpp
+++ b/kit/ChildSession.hpp
@@ -276,6 +276,7 @@ private:
     bool uploadSignedDocument(const char* buffer, int length, const std::vector<std::string>& tokens);
     bool exportSignAndUploadDocument(const char* buffer, int length, const std::vector<std::string>& tokens);
     bool renderShapeSelection(const char* buffer, int length, const std::vector<std::string>& tokens);
+    bool removeTextContext(const char* /*buffer*/, int /*length*/, const std::vector<std::string>& tokens);
 
     void rememberEventsForInactiveUser(const int type, const std::string& payload);
 
diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index 68068abf9..68e27d4d8 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -515,11 +515,7 @@ L.ClipboardContainer = L.Layer.extend({
 			if (l >= count) {
 				this._queuedInput = this._queuedInput.substring(0, l - count);
 			} else {
-				for (var i = 0; i < count; i++) {
-					// Send a UNO backspace keystroke per glyph to be deleted
-					this._sendKeyEvent(8, 1283);
-				}
-
+				this._removeTextContext(count, 0);
 				this._emptyArea();
 			}
 
@@ -913,6 +909,16 @@ L.ClipboardContainer = L.Layer.extend({
 		this._onMSIEKeyDown(ev);
 	},
 
+	// Used in the deleteContentBackward for deleting multiple characters with a single message.
+	_removeTextContext: function _removeTextContext(before, after) {
+		this._map._socket.sendMessage(
+			'removetextcontext id=' +
+			this._map.getWinId() +
+			' before=' + before +
+			' after=' + after
+		);
+	},
+
 	// Tiny helper - encapsulates sending a 'textinput' websocket message.
 	// "type" is either "input" for updates or "end" for commits.
 	_sendCompositionEvent: function _sendCompositionEvent(type, text) {
diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index 85469f6ec..f7c1d35ca 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -426,7 +426,8 @@ bool ClientSession::_handleInput(const char *buffer, int length)
              tokens[0] != "rendershapeselection" &&
              tokens[0] != "removesession" &&
              tokens[0] != "renamefile" &&
-             tokens[0] != "resizewindow")
+             tokens[0] != "resizewindow" &&
+             tokens[0] != "removetextcontext")
     {
         LOG_ERR("Session [" << getId() << "] got unknown command [" << tokens[0] << "].");
         sendTextFrame("error: cmd=" + tokens[0] + " kind=unknown");
commit a06f77ca3a3b1cb2af5490061f2abb9b9282a977
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Wed Jul 3 20:58:04 2019 +0100
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 13:41:50 2019 +0100

    Cleanup the fix - and reduce risk by special-casing for iOS / Android.
    
    Change-Id: I3a86cfcfaa2ce0664aaeccf2c2fb5bd7e980de19

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index 1bcb3a226..68068abf9 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -675,22 +675,26 @@ L.ClipboardContainer = L.Layer.extend({
 		this._fancyLog('beforeinput range', this._lastRangesString());
 
 		// FIXME: a hack - this assumes that nothing changed / no auto-correction inside the Kit.
-		var seltext = window.getSelection();
-		if (!this._isComposing && seltext && seltext.toString() && seltext.toString().length)
+		// FIXME: only mobile for now to reduce risk ...
+		if (window.mode.isMobile() || window.mode.isTablet())
 		{
-			var len = seltext.toString().length;
-			console.log2('Horror meeks3 hack - delete ' + len + ' chars "' + this._queudInput + '"');
-			if (this._queuedInput)
+			var seltext = window.getSelection();
+			if (!this._isComposing && seltext && seltext.toString() && seltext.toString().length > 0)
 			{
-				var size = this._queuedInput.length;
-				var redux = Math.min(size, len);
-				this._queuedInput = this._queuedInput.slice(0,-redux);
-				len -= redux;
-				console.log2('Horror meeks3 hack - removed ' + redux + ' from queued input to "' + this._queuedInput + '"');
+				var len = seltext.toString().length;
+				this._fancyLog('selection overtype', seltext.toString() + ' len ' + len + ' chars "' + this._queuedInput + '"');
+				if (this._queuedInput)
+				{
+					var size = this._queuedInput.length;
+					var redux = Math.min(size, len);
+					this._queuedInput = this._queuedInput.slice(0,-redux);
+					len -= redux;
+					console.log2('queue overtype', 'removed ' + redux + ' from queued input to "' + this._queuedInput + '"');
+				}
+				// FIXME: this is the more hacky bit - if the Kit changed under us.
+				for (var i = 0; i < len; ++i)
+					this._sendKeyEvent(8, 1283);
 			}
-			// FIXME: this is the more hacky bit - if the Kit changed under us.
-			for (var i = 0; i < len; ++i)
-				this._sendKeyEvent(8, 1283);
 		}
 
 		// When trying to delete (i.e. backspace) on an empty textarea, the input event
commit 1fb662f06cb22d9360921ac13536bbc42bfed045
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Wed Jul 3 17:12:00 2019 +0100
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 13:41:45 2019 +0100

    Delete selection hack for testing.
    
    This is pretty grim, but - apparently semi-working.
    
    Change-Id: Ie14c7b0ff9927ecd7eb81716a9b347fa2230146c

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index 0c7735df1..1bcb3a226 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -674,6 +674,25 @@ L.ClipboardContainer = L.Layer.extend({
 		this._fancyLog('beforeinput selection', window.getSelection().toString());
 		this._fancyLog('beforeinput range', this._lastRangesString());
 
+		// FIXME: a hack - this assumes that nothing changed / no auto-correction inside the Kit.
+		var seltext = window.getSelection();
+		if (!this._isComposing && seltext && seltext.toString() && seltext.toString().length)
+		{
+			var len = seltext.toString().length;
+			console.log2('Horror meeks3 hack - delete ' + len + ' chars "' + this._queudInput + '"');
+			if (this._queuedInput)
+			{
+				var size = this._queuedInput.length;
+				var redux = Math.min(size, len);
+				this._queuedInput = this._queuedInput.slice(0,-redux);
+				len -= redux;
+				console.log2('Horror meeks3 hack - removed ' + redux + ' from queued input to "' + this._queuedInput + '"');
+			}
+			// FIXME: this is the more hacky bit - if the Kit changed under us.
+			for (var i = 0; i < len; ++i)
+				this._sendKeyEvent(8, 1283);
+		}
+
 		// When trying to delete (i.e. backspace) on an empty textarea, the input event
 		// won't be fired afterwards. Handle backspace here instead.
 


More information about the Libreoffice-commits mailing list