From 35fec0e4f65e7b20f0ec597708381e3f10cda101 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 31 Oct 2019 10:06:22 +0700 Subject: [PATCH 1/3] fix(web): Fixes #2261, filters nul when deleting context --- web/source/text/kbdInterface.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/web/source/text/kbdInterface.ts b/web/source/text/kbdInterface.ts index 4964c0962c..c7c3a9897b 100644 --- a/web/source/text/kbdInterface.ts +++ b/web/source/text/kbdInterface.ts @@ -842,7 +842,9 @@ namespace com.keyman.text { // We want to control exactly which deadkeys get removed. if(dn > 0) { context = this._BuildExtendedContext(dn, dn, outputTarget); - for(var i=0; i < context.deadContext.length; i++) { + let nulCount = 0; + + for(var i=0; i < context.valContext.length; i++) { var dk = context.deadContext[i]; if(dk) { @@ -851,8 +853,18 @@ namespace com.keyman.text { // Reduce our reported context size. dn--; + } else if(context.valContext[i] == "\uFFFE") { + // Count any `nul` sentinels that would contribute to our deletion count. + nulCount++; } } + + // Prevent attempts to delete nul sentinels, as they don't exist in the actual context. + // (Addresses regression from KMW v 12.0 paired with Developer bug through same version) + let contextLength = context.valContext.length - nulCount; + if(dn > contextLength) { + dn = contextLength; + } } // If a matched deadkey hasn't been deleted, we don't WANT to delete it. From ef8c691381dc494c96601ab6a3106507e523408a Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 31 Oct 2019 10:29:59 +0700 Subject: [PATCH 2/3] fix(web): Adds bounds-check in output targets --- web/source/dom/contentEditable.ts | 5 +++++ web/source/dom/designIFrame.ts | 5 +++++ web/source/dom/input.ts | 4 ++++ web/source/dom/textarea.ts | 4 ++++ web/source/dom/touchAlias.ts | 3 +++ web/source/text/outputTarget.ts | 3 +++ 6 files changed, 24 insertions(+) diff --git a/web/source/dom/contentEditable.ts b/web/source/dom/contentEditable.ts index d6713cadee..00159a1064 100644 --- a/web/source/dom/contentEditable.ts +++ b/web/source/dom/contentEditable.ts @@ -144,6 +144,11 @@ namespace com.keyman.dom { let start = this.getCarets().start; + // Bounds-check on the number of chars to delete. + if(dn > start.offset) { + dn = start.offset; + } + if(start.node.nodeType != 3) { console.warn("Deletion of characters requested without available context!"); return; // No context to delete characters from. diff --git a/web/source/dom/designIFrame.ts b/web/source/dom/designIFrame.ts index b20b2cdbff..42045923fc 100644 --- a/web/source/dom/designIFrame.ts +++ b/web/source/dom/designIFrame.ts @@ -150,6 +150,11 @@ namespace com.keyman.dom { let start = this.getCarets().start; + // Bounds-check on the number of chars to delete. + if(dn > start.offset) { + dn = start.offset; + } + if(start.node.nodeType != 3) { console.warn("Deletion of characters requested without available context!"); return; // No context to delete characters from. diff --git a/web/source/dom/input.ts b/web/source/dom/input.ts index d432f5ce99..bd32120d1e 100644 --- a/web/source/dom/input.ts +++ b/web/source/dom/input.ts @@ -113,6 +113,10 @@ namespace com.keyman.dom { let curText = this.getTextBeforeCaret(); let caret = this.getCaret(); + if(dn > caret) { + dn = caret; + } + this.adjustDeadkeys(-dn); this.setTextBeforeCaret(curText.kmwSubstring(0, this.getCaret() - dn)); this.setCaret(caret - dn); diff --git a/web/source/dom/textarea.ts b/web/source/dom/textarea.ts index de9d82027f..0e0b68bc6d 100644 --- a/web/source/dom/textarea.ts +++ b/web/source/dom/textarea.ts @@ -141,6 +141,10 @@ namespace com.keyman.dom { let curText = this.getTextBeforeCaret(); let caret = this.getCaret(); + if(dn > caret) { + dn = caret; + } + this.adjustDeadkeys(-dn); this.setTextBeforeCaret(curText.kmwSubstring(0, this.getCaret() - dn)); this.setCaret(caret - dn); diff --git a/web/source/dom/touchAlias.ts b/web/source/dom/touchAlias.ts index e64948235b..954c521055 100644 --- a/web/source/dom/touchAlias.ts +++ b/web/source/dom/touchAlias.ts @@ -50,6 +50,9 @@ namespace com.keyman.dom { deleteCharsBeforeCaret(dn: number): void { if(dn > 0) { let curText = this.getTextBeforeCaret(); + if(this.getDeadkeyCaret() < dn) { + dn = this.getDeadkeyCaret(); + } this.adjustDeadkeys(-dn); this.root.setTextBeforeCaret(curText.kmwSubstring(0, this.root.getTextCaret() - dn)); } diff --git a/web/source/text/outputTarget.ts b/web/source/text/outputTarget.ts index fcdf05f068..ee6fe10ed7 100644 --- a/web/source/text/outputTarget.ts +++ b/web/source/text/outputTarget.ts @@ -329,6 +329,9 @@ namespace com.keyman.text { deleteCharsBeforeCaret(dn: number): void { if(dn >= 0) { + if(dn > this.caretIndex) { + dn = this.caretIndex; + } this.text = this.text.kmwSubstr(0, this.caretIndex - dn) + this.getTextAfterCaret(); this.caretIndex -= dn; } From dfcfa2343ba7664e5f068e4ee17864e826acc0e6 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 31 Oct 2019 11:48:00 +0700 Subject: [PATCH 3/3] feat(web): bounds check unit test --- web/unit_tests/cases/element_interfaces.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/web/unit_tests/cases/element_interfaces.js b/web/unit_tests/cases/element_interfaces.js index 601469a6ba..e1ed36b69c 100644 --- a/web/unit_tests/cases/element_interfaces.js +++ b/web/unit_tests/cases/element_interfaces.js @@ -31,6 +31,7 @@ if(typeof InterfaceTests == 'undefined') { InterfaceTests.Input.resetWithText = function(pair, string) { pair.elem.value = string; + pair.wrapper.invalidateSelection(); pair.elem.setSelectionRange(0, 0); } @@ -76,6 +77,7 @@ if(typeof InterfaceTests == 'undefined') { InterfaceTests.TextArea.resetWithText = function(pair, string) { pair.elem.value = string; + pair.wrapper.invalidateSelection(); pair.elem.setSelectionRange(0, 0); } @@ -803,6 +805,14 @@ if(typeof InterfaceTests == 'undefined') { pair.wrapper.deleteCharsBeforeCaret(2); assert.equal(pair.wrapper.getText(), Apple.mixed.substr(0, 1) + Apple.mixed.substr(4), "Error deleting context chars from a mixed SMP string"); String.kmwEnableSupplementaryPlane(false); + + String.kmwEnableSupplementaryPlane(false); + testObj.resetWithText(pair, Apple.normal); + testObj.setCaret(pair, 4); + pair.wrapper.deleteCharsBeforeCaret(7); + assert.equal(pair.wrapper.getText(), "e", "Bounds-check on deletion range failed; context improperly deleted."); + String.kmwEnableSupplementaryPlane(false); + pair.wrapper.invalidateSelection(); } InterfaceTests.Tests.deleteCharsBeforeCaretWithSelection = function(testObj) {