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 <[email protected]>
AuthorDate: Wed Aug 28 21:26:46 2019 -0400
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Mon Aug 19 07:52:33 2019 -0400
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Thu Aug 15 16:29:24 2019 -0400
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Thu Oct 3 14:19:59 2019 +0100
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Thu Oct 3 14:09:03 2019 +0100
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Mon Jul 29 17:46:42 2019 +0200
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Tue Jul 23 21:50:55 2019 +0200
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Tue Jul 23 20:41:16 2019 +0100
Commit:     Michael Meeks <[email protected]>
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 &nbsp; 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 <[email protected]>
AuthorDate: Tue Jul 23 18:19:40 2019 +0100
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Tue Jul 23 17:59:43 2019 +0100
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Tue Jul 23 17:35:50 2019 +0100
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Tue Jul 23 15:43:41 2019 +0100
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Mon Jul 22 20:46:46 2019 +0100
Commit:     Michael Meeks <[email protected]>
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(/&nbsp;/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 <[email protected]>
AuthorDate: Mon Jul 22 17:06:55 2019 +0100
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Mon Jul 22 17:01:03 2019 +0100
Commit:     Michael Meeks <[email protected]>
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(/&nbsp;/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 <[email protected]>
AuthorDate: Thu Jul 4 15:53:23 2019 +0200
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Wed Jul 10 10:29:50 2019 +0200
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Wed Jul 3 20:58:04 2019 +0100
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Wed Jul 3 17:12:00 2019 +0100
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Wed Jun 19 09:36:14 2019 -0400
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Tue Jun 18 23:49:01 2019 +0200
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Wed Jun 19 14:26:55 2019 +0200
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Wed Jun 19 12:53:05 2019 +0200
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Wed Jun 19 12:01:23 2019 +0200
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Wed Jun 19 11:22:06 2019 +0200
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Wed Jun 19 11:02:13 2019 +0200
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Wed Jun 19 10:28:20 2019 +0200
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Tue Jun 18 18:14:24 2019 +0200
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Tue Jun 18 15:20:02 2019 +0200
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Tue Jun 18 09:53:51 2019 +0100
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Tue Jun 18 09:27:46 2019 +0100
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Tue Jul 2 22:41:57 2019 +0100
Commit:     Michael Meeks <[email protected]>
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 <[email protected]>
AuthorDate: Tue Jul 2 22:20:31 2019 +0100
Commit:     Michael Meeks <[email protected]>
CommitDate: Thu Oct 3 14:50:17 2019 +0100


... etc. - the rest is truncated
_______________________________________________
Libreoffice-commits mailing list
[email protected]
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to