[Libreoffice-commits] online.git: 78 commits - bundled/include kit/ChildSession.cpp kit/ChildSession.hpp loleaflet/build loleaflet/src loolwsd.xml.in wsd/ClientSession.cpp wsd/LOOLWSD.cpp

Henry Castro (via logerrit) logerrit at kemper.freedesktop.org
Thu Oct 3 13:50:42 UTC 2019


 bundled/include/LibreOfficeKit/LibreOfficeKit.h   |    6 
 bundled/include/LibreOfficeKit/LibreOfficeKit.hxx |   13 
 kit/ChildSession.cpp                              |   28 
 kit/ChildSession.hpp                              |    1 
 loleaflet/build/deps.js                           |    7 
 loleaflet/src/control/Control.LokDialog.js        |  120 ---
 loleaflet/src/control/Control.MobileInput.js      |  343 -----------
 loleaflet/src/control/Control.Toolbar.js          |    2 
 loleaflet/src/core/Browser.js                     |    4 
 loleaflet/src/core/Socket.js                      |    7 
 loleaflet/src/layer/marker/ClipboardContainer.js  |  666 ++++++++++++++++++----
 loleaflet/src/layer/tile/TileLayer.js             |   63 +-
 loleaflet/src/map/Map.js                          |   81 ++
 loleaflet/src/map/handler/Map.Keyboard.js         |  229 +++----
 loleaflet/src/map/handler/Map.TouchGesture.js     |    5 
 loolwsd.xml.in                                    |    2 
 wsd/ClientSession.cpp                             |    3 
 wsd/LOOLWSD.cpp                                   |    2 
 18 files changed, 854 insertions(+), 728 deletions(-)

New commits:
commit 974d02fcca70d96cb09b71fc1e33c0950ae50b29
Author:     Henry Castro <hcastro at collabora.com>
AuthorDate: Wed Aug 28 21:26:46 2019 -0400
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 14:50:17 2019 +0100

    loleaflet: mobile: fix the first typed character after opening the document
    
    calling the function setSelectionRange is an implicit keyboard focus.
    Only enable when the text area has the focus
    
    Change-Id: Ic58abd3fc555ad9a0a08a01041f7aeb5367d271b

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index 643e00567..4d996ffb8 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -507,7 +507,7 @@ L.ClipboardContainer = L.Layer.extend({
 	// always catch deleteContentBackward/deleteContentForward input events
 	// (some combination of browser + input method don't fire those on an
 	// empty contenteditable).
-	_emptyArea: function _emptyArea() {
+	_emptyArea: function _emptyArea(noSelect) {
 		this._fancyLog('empty-area');
 
 		this._ignoreInputCount++;
@@ -524,7 +524,7 @@ L.ClipboardContainer = L.Layer.extend({
 		this._textArea.value = this._preSpaceChar + this._postSpaceChar;
 
 		// avoid setting the focus keyboard
-		if (document.activeElement === this._textArea) {
+		if (!noSelect) {
 			this._textArea.setSelectionRange(1, 1);
 
 			if (this._hasWorkingSelectionStart === undefined)
@@ -566,7 +566,7 @@ L.ClipboardContainer = L.Layer.extend({
 		this._fancyLog('abort-composition', ev.type);
 		if (this._isComposing)
 			this._isComposing = false;
-		this._emptyArea();
+		this._emptyArea(document.activeElement !== this._textArea);
 	},
 
 	_onKeyDown: function _onKeyDown(ev) {
commit e33fc028766ab3d3d321019aa33d09debfa38009
Author:     Henry Castro <hcastro at collabora.com>
AuthorDate: Mon Aug 19 07:52:33 2019 -0400
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 14:50:17 2019 +0100

    loleaflet: mobile: removes keyboard focus when the graphic is selected
    
    Change-Id: Iced49475ebf9af5508059f5d6e223e99d1187649

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index e7f7a2ed7..643e00567 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -522,9 +522,14 @@ L.ClipboardContainer = L.Layer.extend({
 		this._lastContent = [];
 
 		this._textArea.value = this._preSpaceChar + this._postSpaceChar;
-		this._textArea.setSelectionRange(1, 1);
-		if (this._hasWorkingSelectionStart === undefined)
-			this._hasWorkingSelectionStart = (this._textArea.selectionStart === 1);
+
+		// avoid setting the focus keyboard
+		if (document.activeElement === this._textArea) {
+			this._textArea.setSelectionRange(1, 1);
+
+			if (this._hasWorkingSelectionStart === undefined)
+				this._hasWorkingSelectionStart = (this._textArea.selectionStart === 1);
+		}
 
 		this._fancyLog('empty-area-end');
 
diff --git a/loleaflet/src/map/handler/Map.TouchGesture.js b/loleaflet/src/map/handler/Map.TouchGesture.js
index fcdab2bd4..ae35507fa 100644
--- a/loleaflet/src/map/handler/Map.TouchGesture.js
+++ b/loleaflet/src/map/handler/Map.TouchGesture.js
@@ -239,7 +239,9 @@ L.Map.TouchGesture = L.Handler.extend({
 		this._map._docLayer._postMouseEvent('buttondown', mousePos.x, mousePos.y, 1, 1, 0);
 		this._map._docLayer._postMouseEvent('buttonup', mousePos.x, mousePos.y, 1, 1, 0);
 
-		if (!this._map.hasFocus()) {
+		if (this._state === L.Map.TouchGesture.MARKER || this._state === L.Map.TouchGesture.GRAPHIC) {
+			this._map._clipboardContainer.blur();
+		} else {
 			this._map.focus();
 		}
 	},
commit 1240da475fae851668a8103f2461910d99a829ce
Author:     Henry Castro <hcastro at collabora.com>
AuthorDate: Thu Aug 15 16:29:24 2019 -0400
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 14:50:17 2019 +0100

    loleaflet: mobile: hide the cursor marker when exists text selection
    
    Change-Id: Ib0a5c74567e1a0a71c53d741aa6c44a09b6b0fe2

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index ee90597d1..e7f7a2ed7 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -242,7 +242,11 @@ L.ClipboardContainer = L.Layer.extend({
 
 		// Move and display under-caret marker
 		if (L.Browser.touch) {
-			this._cursorHandler.setLatLng(bottom).addTo(this._map);
+			if (this._map._docLayer._selections.getLayers().length === 0) {
+				this._cursorHandler.setLatLng(bottom).addTo(this._map);
+			} else {
+				this._map.removeLayer(this._cursorHandler);
+			}
 		}
 
 		// Move the hidden text area with the cursor
commit e39fb629b48a4675608103d87ea9ba3af008337b
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Thu Oct 3 14:19:59 2019 +0100
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 14:50:17 2019 +0100

    Remove obsolete and unhelpful method.
    
    Fixes up 8440e286c merge.
    
    Change-Id: I27f16d36d135feae10de6d1db732259f81afd1fc

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index 7239126e3..ee90597d1 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -157,17 +157,6 @@ L.ClipboardContainer = L.Layer.extend({
 		return arr;
 	},
 
-	setValue: function(val) {
-		// console.log('clipboard setValue: ', val);
-		if (this._legacyArea) {
-			var tmp = document.createElement('div');
-			tmp.innerHTML = val;
-			this._textArea.value = tmp.innerText || tmp.textContent || '';
-		} else {
-			this._textArea.innerHTML = val;
-		}
-	},
-
 	update: function() {
 		if (this._container && this._map && this._latlng) {
 			var position = this._map.latLngToLayerPoint(this._latlng).round();
diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js
index dd923df61..db2da511d 100644
--- a/loleaflet/src/layer/tile/TileLayer.js
+++ b/loleaflet/src/layer/tile/TileLayer.js
@@ -655,10 +655,6 @@ L.TileLayer = L.GridLayer.extend({
 		// messages during text composition, and resetting the contents
 		// of the clipboard container mid-composition will easily break it.
 		var formula = textMsg.substring(13);
-		if (!this._map['wopi'].DisableCopy) {
-			this._map._clipboardContainer.setValue(formula);
-			this._map._clipboardContainer.select();
-		}
 		this._lastFormula = formula;
 		this._map.fire('cellformula', {formula: formula});
 	},
commit 2210fddb2d89ed96ed425671423fdef1eebbf6a0
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Thu Oct 3 14:09:03 2019 +0100
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 14:50:17 2019 +0100

    Remove unused code.
    
    Change-Id: I7d75cd570411a3e9b596b853da9ebc77b703ee03

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index 17c420397..7239126e3 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -131,16 +131,6 @@ L.ClipboardContainer = L.Layer.extend({
 		this._textArea.select();
 	},
 
-	warnCopyPaste: function() {
-		var self = this;
-		vex.dialog.alert({
-			unsafeMessage: _('<p>Your browser has very limited access to the clipboard, so use these keyboard shortcuts:<ul><li><b>Ctrl+C</b>: For copying.</li><li><b>Ctrl+X</b>: For cutting.</li><li><b>Ctrl+V</b>: For pasting.</li></ul></p>'),
-			callback: function () {
-				self._map.focus();
-			}
-		});
-	},
-
 	getValue: function() {
 		var value = this._textArea.value;
 		return value;
commit 3783e29db574a9e4e2c49e0bf3db581301603fc2
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 14:50:17 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 c41f0772f..b00ff806d 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 917c357d7e3009ed42876761119467b863471462
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 14:50:17 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 b7a842ef4..c41f0772f 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 51779f9f002bfe4645afebc31227e8d8e2bf297c
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 14:50:17 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 20e20cb65..17c420397 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;
@@ -358,19 +363,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;
 			}
 		}
 	},
@@ -393,19 +402,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();
@@ -416,9 +430,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)
@@ -525,7 +538,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 f850d01d2641d3ed769367619f7608f44265e2bc
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 14:50:17 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 b589683f3..20e20cb65 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -481,14 +481,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 a78551ce02cfa67eab2b5baa6b4c6c6eaecd9301
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 14:50:17 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 631456867..b589683f3 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 = ' ';
@@ -310,6 +311,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;
@@ -357,14 +359,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;
 			}
 		}
 	},
@@ -409,7 +415,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 103a039fd21d27222d6a9c66e8eff33fc157820c
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 14:50:17 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 f9f5ccade..631456867 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 = ' ';
@@ -91,6 +91,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);
@@ -308,6 +309,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;
@@ -351,6 +353,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();
@@ -436,7 +454,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));
@@ -503,9 +520,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 f68d56735c280923a17b7ba972876e2976cada0a
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 14:50:17 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 ed6f3bbbc..f9f5ccade 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -389,10 +389,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 67eafba6b7a5ed06acea69da94e1df5bb933b654
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 14:50:17 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 1d9cbcaba..ed6f3bbbc 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;
@@ -86,6 +94,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);
 
@@ -127,8 +136,6 @@ L.ClipboardContainer = L.Layer.extend({
 
 	getValue: function() {
 		var value = this._textArea.value;
-		// kill unwanted entities
-		value = value.replace(/ /g, ' ');
 		return value;
 	},
 
@@ -303,6 +310,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)
@@ -328,6 +338,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 ',
@@ -340,7 +352,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) {
@@ -348,22 +360,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);
@@ -377,18 +411,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)
@@ -439,15 +492,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--;
 	},
@@ -485,66 +541,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 73839f0dffe4c9c31c782cf3a634e0b824446a47
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 14:50:17 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 b1ccda130..1d9cbcaba 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -185,6 +185,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 8a2e7d8c8a6c0a0981cf6d8b8e5673e168a0e6be
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 14:50:17 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..b1ccda130 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,62 +70,29 @@ L.ClipboardContainer = L.Layer.extend({
 	},
 
 	_onFocusBlur: function(ev) {
-// 		console.log(ev.type, performance.now(), ev);
-
-		if (this.dontBlur && ev.type == 'blur') {
-			this._map.focus();
-			this.dontBlur = false;
-			return;
-		}
-
-		if (this.dontBlur && ev.type == 'blur') {
-			this._map.focus();
-			this.dontBlur = false;
-			return;
-		}
+		this._fancyLog(ev.type, '');
 
 		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 +112,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 +126,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 +178,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 +186,8 @@ L.ClipboardContainer = L.Layer.extend({
 		this._textArea.setAttribute('spellcheck', 'false');
 
 		this._setupStyles();
+
+		this._emptyArea();
 	},
 
 	_setupStyles: function() {
@@ -346,42 +264,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 +294,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 +334,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);
 
-	// 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);
+		this._lastContent = content;
+
+		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 +393,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 +428,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));
-
-			}
-		} 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.');
-			}
-		}
-	},
-
-	// 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);
-			}
-		}
+		this._fancyLog('empty-area');
 
-		// 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._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.
 
-	_queueInput: function _queueInput(text) {
-		this._map.notifyActive();
+		console.log('Set old/lastContent to empty');
+		this._lastContent = [];
 
-		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;
-		}
+		this._textArea.value = '\xa0 ';
+		/// TODO: Check that this selection method works with MSIE11
+		this._textArea.setSelectionRange(1, 1);
 
-		//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 +463,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 +474,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 +557,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 +576,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 +588,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 586e8159a131f2860b7b632cc62994d1d989169e
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 14:50:17 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 8741339064da810cfaa7af6a98b06a0da7de2932
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 14:50:17 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..7425e4fac 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(getLock());
+    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 1f80d99a7e373685bc52f4218d7a94b12b4aec25
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 14:50:17 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 2ba1e6a6ae2decdec82280f8cb41d448143507c7
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 14:50:17 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.
 
commit e97e3e6a7785ff135e6b9db1500dce4ba5527ea2
Author:     Ashod Nakashian <ashod.nakashian at collabora.co.uk>
AuthorDate: Wed Jun 19 09:36:14 2019 -0400
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 14:50:17 2019 +0100

    leaflet: correctly handle backspace in dialogs
    
    The workaround for handling backspace caused
    double-deletes in dialogs.
    
    Change-Id: I85f1e1e89b7b802c24960b6f9b7b7e1e60af90a9

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index 2a575bf66..0c7735df1 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -680,9 +680,8 @@ L.ClipboardContainer = L.Layer.extend({
 		// 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.
-		// NOTE: Ideally this should never happen, as the textarea/contenteditable
-		// is initialized with two non-breaking spaces when "emptied".
-		if (!this._hasInputType || (this._lastRangesString() === '0-0')) {
+		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') {
commit ae22aa322fbf3b418877d987630e3b17860b7c6c
Author:     Szymon Kłos <szymon.klos at collabora.com>
AuthorDate: Tue Jun 18 23:49:01 2019 +0200
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 14:50:17 2019 +0100

    Do not blur just before focus to keep keyboard
    
    This also reverts: 80768f782d867bf03f49a028c876b647a05d8662
    (timeouts for focus and blur events)
    
    Change-Id: Ib98af7bd397954987e411473f611935a7e336ce6

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index b34853b88..2a575bf66 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -76,6 +76,14 @@ 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;
 	},
 
 	onAdd: function() {
@@ -121,6 +129,12 @@ L.ClipboardContainer = L.Layer.extend({
 			return;
 		}
 
+		if (this.dontBlur && ev.type == 'blur') {
+			this._map.focus();
+			this.dontBlur = false;
+			return;
+		}
+
 		var onoff = (ev.type == 'focus' ? L.DomEvent.on : L.DomEvent.off).bind(L.DomEvent);
 
 		onoff(this._textArea, 'compositionstart', this._onCompositionStart, this);
@@ -172,13 +186,11 @@ L.ClipboardContainer = L.Layer.extend({
 			console.log('EPIC HORRORS HERE');
 			return;
 		}
-		var that = this;
-		setTimeout(function() { that._textArea.focus(); }, 10);
+		this._textArea.focus();
 	},
 
 	blur: function() {
-		var that = this;
-		setTimeout(function() { that._textArea.blur(); }, 10);
+		this._textArea.blur();
 	},
 
 	// Marks the content of the textarea/contenteditable as selected,
commit b7be199357f2d02ec0096fa7eb1b2ba65a15aa41
Author:     Iván Sánchez Ortega <ivan.sanchez at collabora.com>
AuthorDate: Wed Jun 19 14:26:55 2019 +0200
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 14:50:17 2019 +0100

    loleaflet: ClipboardContainer: Re-enable range fetching on 'beforeinput' events
    
    Change-Id: I1d7d18b36a2152e91fe8c9db21fa7a2e9f4dc935

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index f076d98b1..b34853b88 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -651,8 +651,10 @@ L.ClipboardContainer = L.Layer.extend({
 	// particularly when the textarea/contenteditable is empty, but
 	// only in some configurations.
 	_onBeforeInput: function _onBeforeInput(ev) {
-		var selection = window.getSelection().toString();
 		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;
commit ae350549a811a103c1a61af79d2abd05c902eff9
Author:     Iván Sánchez Ortega <ivan.sanchez at collabora.com>
AuthorDate: Wed Jun 19 12:53:05 2019 +0200
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 14:50:17 2019 +0100

    loleaflet: ClipboardContainer should behave the same in Chrome/ium...
    
    ...than in webkit-like Android WebView
    
    Change-Id: I0063e1b67a5a705eb54d6bd5e977e36c8969b71d

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index 0eb4400c9..f076d98b1 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -727,13 +727,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) {
-		this._map.notifyActive();
 		// 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 && !L.Browser.gecko)
-		) {
+		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);
commit 0dc71cd79980783ad4a89b4ef90ba666fa9e42b7
Author:     Iván Sánchez Ortega <ivan.sanchez at collabora.com>
AuthorDate: Wed Jun 19 12:01:23 2019 +0200
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 14:50:17 2019 +0100

    loleaflet: ClipboardContainer fix innerHTML syntax
    
    Change-Id: I3aa81d690d4b4ea3627525f5f5e53ffeecfe9b81

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index bb1c072aa..0eb4400c9 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -411,7 +411,7 @@ L.ClipboardContainer = L.Layer.extend({
 			} else {
 				if (
 					this._textArea.textContent.length === 0 &&
-					this._textArea.textContent.innerHTML.indexOf('<br>') !== -1
+					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
commit ff3a5a18474b03602ac8dc6305761031f01b352e
Author:     Iván Sánchez Ortega <ivan.sanchez at collabora.com>
AuthorDate: Wed Jun 19 11:22:06 2019 +0200
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 14:50:17 2019 +0100

    loleaflet: ClipboardContainer hack for pressing enter on Android WebView
    
    Change-Id: I735fb1a856fdd1de1dc77044bb169216df6ba64f

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index 5c16c9ec3..bb1c072aa 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -404,12 +404,22 @@ L.ClipboardContainer = L.Layer.extend({
 		this._lastInputType = ev.inputType;
 
 		if (!('inputType' in ev)) {
-			// Legacy MSIE or Android Webkit, just send the contents of the
+			// 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 {
-				this._sendText(this._textArea.textContent);
+				if (
+					this._textArea.textContent.length === 0 &&
+					this._textArea.textContent.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') {
commit f0c5b6d1729806cd7137d2f1f4662ecf145cb906
Author:     Iván Sánchez Ortega <ivan.sanchez at collabora.com>
AuthorDate: Wed Jun 19 11:02:13 2019 +0200
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 14:50:17 2019 +0100

    loleaflet: ClipboardContainer handle composition mode for legacy input events
    
    Change-Id: I3db3e790f75de90b126ea0e63771b6035f91b0b4

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index 785e6b0a7..5c16c9ec3 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -406,9 +406,12 @@ L.ClipboardContainer = L.Layer.extend({
 		if (!('inputType' in ev)) {
 			// Legacy MSIE or Android Webkit, just send the contents of the
 			// container and clear it.
-			this._sendText(this._textArea.textContent);
-			this._emptyArea();
-
+			if (this._isComposing) {
+				this._sendCompositionEvent('input', this._textArea.textContent);
+			} 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'
commit 4a20744c0c725ae3756981bec10a709d4b7ab1e6
Author:     Iván Sánchez Ortega <ivan.sanchez at collabora.com>
AuthorDate: Wed Jun 19 10:28:20 2019 +0200
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 14:50:17 2019 +0100

    loleaflet: ClipboardContainer: Assume legacy input events on Android Webkit as well
    
    Change-Id: Iaa04f808940afaedc12c48b48c722673b3b406ab

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index 9f782e392..785e6b0a7 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -404,16 +404,11 @@ L.ClipboardContainer = L.Layer.extend({
 		this._lastInputType = ev.inputType;
 
 		if (!('inputType' in ev)) {
-			if (this._isComposing) {
-				this._sendCompositionEvent('input', this._textArea.textContent);
-			} else {
-				// Legacy MSIE, Android WebView or FFX < 66, just send the contents of the
-				// container and clear it.
-				if (this._textArea.textContent.length !== 0) {
-					this._sendText(this._textArea.textContent);
-				}
-				this._emptyArea();
-			}
+			// Legacy MSIE or Android Webkit, just send the contents of the
+			// container and clear it.
+			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'
commit 1de1784942ba1cb857cde95546dddb2dbb11fa36
Author:     Szymon Kłos <szymon.klos at collabora.com>
AuthorDate: Tue Jun 18 18:14:24 2019 +0200
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 14:50:17 2019 +0100

    Set timeout for focus in focus events
    
    This allows events to occur in order.
    
    Change-Id: I30701dcdcb5758ea2d5f69a93203641679823d8c

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index 13d846781..9f782e392 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -172,11 +172,13 @@ L.ClipboardContainer = L.Layer.extend({
 			console.log('EPIC HORRORS HERE');
 			return;
 		}
-		this._textArea.focus();
+		var that = this;
+		setTimeout(function() { that._textArea.focus(); }, 10);
 	},
 
 	blur: function() {
-		this._textArea.blur();
+		var that = this;
+		setTimeout(function() { that._textArea.blur(); }, 10);
 	},
 
 	// Marks the content of the textarea/contenteditable as selected,
commit d525b4625690a86a86f451ac51a05778ff5c2002
Author:     Iván Sánchez Ortega <ivan.sanchez at collabora.com>
AuthorDate: Tue Jun 18 15:20:02 2019 +0200
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 14:50:17 2019 +0100

    loleaflet: Explicitly relay keyboard events from LokDialog to Keyboard handler
    
    Change-Id: Icaa33537313c73286937bdeeef595bcd82037f95

diff --git a/loleaflet/src/map/handler/Map.Keyboard.js b/loleaflet/src/map/handler/Map.Keyboard.js
index 18ad2f5d7..0ef5d1a66 100644
--- a/loleaflet/src/map/handler/Map.Keyboard.js
+++ b/loleaflet/src/map/handler/Map.Keyboard.js
@@ -181,22 +181,13 @@ L.Map.Keyboard = L.Handler.extend({
 		L.DomEvent.off(this._map.getContainer(), 'keydown keyup keypress', this._onKeyDown, this);
 	},
 
-	/*
-	 * Returns true whenever the key event shall be ignored.
-	 * This means shift+insert and shift+delete (or "insert or delete when holding
-	 * shift down"). Those events are handled elsewhere to trigger "cut" and
-	 * "paste" events, and need to be ignored in order to avoid double-handling them.
-	 */
-	_ignoreKeyEvent: function(e) {
-		var shift = e.originalEvent.shiftKey;
-		if ('key' in e.originalEvent) {
-			var key = e.originalEvent.key;
-			return (shift && (key === 'Delete' || key === 'Insert'));
-		} else {
-			// keyCode is not reliable in AZERTY/DVORAK keyboard layouts, is used
-			// only as a fallback for MSIE8.
-			var keyCode = e.originalEvent.keyCode;
-			return (shift && (keyCode === 45 || keyCode === 46));
+	_ignoreKeyEvent: function(ev) {
+		var shift = ev.shiftKey ? this.keyModifier.shift : 0;
+		if (shift && (ev.keyCode === 45 || ev.keyCode === 46)) {
+			// don't handle shift+insert, shift+delete
+			// These are converted to 'cut', 'paste' events which are
+			// automatically handled by us, so avoid double-handling
+			return true;
 		}
 	},
 
@@ -253,6 +244,7 @@ L.Map.Keyboard = L.Handler.extend({
 	// _handleKeyEvent - checks if the given keyboard event shall trigger
 	// a message to lowsd, and calls the given keyEventFn(type, charcode, keycode)
 	// callback if so.
+	// Called from private _onKeyDown
 	_handleKeyEvent: function (ev, keyEventFn) {
 		this._map.notifyActive();
 		if (this._map.slideShow && this._map.slideShow.fullscreen) {
commit 8b080fee2f86dbcc7d77b5eedeab468d1f8388f6
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Tue Jun 18 09:53:51 2019 +0100
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 14:50:17 2019 +0100

    Avoid using the read-only / edit toggle as a show keyboard button.
    
    Change-Id: I1c4e88c1824a8aef4aa2bb2e1e18943d95202bb0

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index a6631cccb..13d846781 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -156,10 +156,13 @@ L.ClipboardContainer = L.Layer.extend({
 
 		this._map.notifyActive();
 
-		if (ev.type === 'blur' && this._isComposing) {
-			/// TODO: Set this._compositionText
-			this._queueInput(this._compositionText);
-			this._abortComposition(ev);
+		if (ev.type === 'blur') {
+			if (this._isComposing) {
+				this._queueInput(this._compositionText);
+			}
+			this._abortComposition();
+		} else {
+			this._winId = 0;
 		}
 	},
 
commit 0c749e0f597509122913a9b4a08c6f07e8fda517
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Tue Jun 18 09:27:46 2019 +0100
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 14:50:17 2019 +0100

    Default focus dialogs.
    
    Change-Id: Idc37cdfbd84bbdcea90c96175091aff6d3026da6

diff --git a/loleaflet/src/control/Control.LokDialog.js b/loleaflet/src/control/Control.LokDialog.js
index b0404a593..b7a842ef4 100644
--- a/loleaflet/src/control/Control.LokDialog.js
+++ b/loleaflet/src/control/Control.LokDialog.js
@@ -399,8 +399,7 @@ L.Control.LokDialog = L.Control.extend({
 	focus: function(dlgId, force) {
 		if (!force && (!this._isOpen(dlgId) || !this._dialogs[dlgId].cursorVisible))
 			return;
-		this._map.setWinId(dlgId);
-		this._map.getClipboardContainer().focus();
+		this._dialogs[dlgId].input.focus();
 	},
 
 	_setCanvasWidthHeight: function(canvas, width, height) {
commit 7e38e8fc3cb00a26a02a45105bf6e534b381746c
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Tue Jul 2 22:41:57 2019 +0100
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 14:50:17 2019 +0100

    input: avoid exceptions during logging.
    
    Change-Id: I4dfc44688f7320c9294018904732e4343229de9b

diff --git a/loleaflet/src/layer/marker/ClipboardContainer.js b/loleaflet/src/layer/marker/ClipboardContainer.js
index c3f070932..a6631cccb 100644
--- a/loleaflet/src/layer/marker/ClipboardContainer.js
+++ b/loleaflet/src/layer/marker/ClipboardContainer.js
@@ -370,6 +370,12 @@ L.ClipboardContainer = L.Layer.extend({
 	},
 
 	_fancyLog: function _fancyLog(type, payload) {
+		// Avoid unhelpful exceptions
+		if (payload === undefined)
+			payload = 'undefined';
+		else if (payload === null)
+			payload = 'null';
+
 		// Save to downloadable log
 		L.Log.log(payload.toString(), 'INPUT');
 
commit 0c218028eb243919a66e1b3bcc42c47aa5ecde94
Author:     Michael Meeks <michael.meeks at collabora.com>
AuthorDate: Tue Jul 2 22:20:31 2019 +0100
Commit:     Michael Meeks <michael.meeks at collabora.com>
CommitDate: Thu Oct 3 14:50:17 2019 +0100


... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list