From 16622c83603376cfa9d15d58f91448e4e52538ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 5 Jan 2022 15:31:08 +0100 Subject: [PATCH 1/2] documentation: work around various Android Chrome autocompletion issues. There's two bugs, basically: 1. When using the Android virtual keyboard, `event.key` is always `Unidentified` and `event.code` always 229, meaning we have no way to detect if some text got inserted (in which case autocompletion should trigger) or some was deleted (in which case it shouldn't as that'd mean the selected autocompletion text erased from the back would get immediately added again). To work around that, we detect Shitty Android and defer the autocompletion enablement to an inputEvent that's triggered right after and which contains the data we need. 2. Calling setSelectionRange() directly in code will make the browser *render* the selection, but the remote keyboard isn't for some reason aware of the fact, and thus pressing backspace on that keyboard won't delete anything, only remove the highlight. To fix this, one has to defer the call to setSelectionRange() to a timeout, then it behaves properly. And then, then there's Firefox Mobile, which is a whole other problem. That gets tackled in the next commit. --- documentation/search.js | 102 +++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 60 deletions(-) diff --git a/documentation/search.js b/documentation/search.js index 4b65e9ab..694bca7b 100644 --- a/documentation/search.js +++ b/documentation/search.js @@ -51,6 +51,15 @@ var Search = { input (so not deletion, cut, or anything else). This is flipped in the onkeypress event and reset after each oninput event. */ autocompleteNextInputEvent: false, + /* On Android using a virtual keyboard, all `event.key`s are reported as + `Unidentified` with `event.code` 229 and thus the onkeydown / onKeyup + events are useless. See this WONTFIX bug for details: + https://bugs.chromium.org/p/chromium/issues/detail?id=118639 + Instead, to make the autocompletion working when it should and not + working when it shouldn't, when such event is detected we defer the + handling to the input event, which is fired right after and has the data + we need. */ + fallbackBuggyAndroidToNextInputEvent: false, init: function(buffer, maxResults) { let view = new DataView(buffer); @@ -535,9 +544,15 @@ var Search = { if(this.autocompleteNextInputEvent && resultsSuggestedTabAutocompletion[1].length && searchInput.selectionEnd == searchInput.value.length) { let suggestedTabAutocompletion = this.fromUtf8(resultsSuggestedTabAutocompletion[1]); - let lengthBefore = searchInput.value.length; - searchInput.value += suggestedTabAutocompletion; - searchInput.setSelectionRange(lengthBefore, searchInput.value.length); + /* Delay-set the selected autocompletion text. Witout this we + hit a NASTY bug on Android Chrome where the selection is + displayed but deleting it via the virtual keyboard does + nothing. */ + window.setTimeout(function() { + let lengthBefore = searchInput.value.length; + searchInput.value += suggestedTabAutocompletion; + searchInput.setSelectionRange(lengthBefore, searchInput.value.length); + }); } /* Nothing found */ @@ -647,14 +662,36 @@ function copyToKeyboard(text) { work is beyond me. */ /* istanbul ignore if */ if(typeof document !== 'undefined') { document.getElementById('search-input').oninput = function(event) { + /* Figure out if to autocomplete on Androids using a virtual keyboard, + as the onkeydown event fired just before was useless. See the + note about `Unidentified` keys below. */ + if(window.location.hash == '#search' && document.activeElement.id == 'search-input' && Search.fallbackBuggyAndroidToNextInputEvent) { + if(event.inputType.startsWith('insert')) + Search.autocompleteNextInputEvent = true; + else + Search.autocompleteNextInputEvent = false; + Search.fallbackBuggyAndroidToNextInputEvent = false; + } + Search.searchAndRender(document.getElementById('search-input').value); }; document.onkeydown = function(event) { /* Search shown */ if(window.location.hash == '#search') { + /* We're on Android and using a virtual keyboard, which reports all + `event.key`s as `Unidentified` with `event.code` 229 and thus we + have no way to do anything. See this WONTFIX bug for details: + https://bugs.chromium.org/p/chromium/issues/detail?id=118639 + We don't expect the virtual keyboard to be used for stuff like + moving up/down in the result list or copying links, so the only + thing to be handled there is the autocompletion enable/disable, + which we'll defer to the immediately following input event. */ + if(event.key == 'Unidentified') { + Search.fallbackBuggyAndroidToNextInputEvent = true; + /* Close the search */ - if(event.key == 'Escape') { + } else if(event.key == 'Escape') { hideSearch(); /* Focus the search input, if not already, using T or Tab */ @@ -739,62 +776,7 @@ if(typeof document !== 'undefined') { worst case the autocompletion won't be allowed ever, which is much more acceptable behavior than having no ability to disable it and annoying the users. */ - } else if(event.key != 'Backspace' && event.key != 'Delete' && !event.metaKey && (!event.ctrlKey || event.altKey) - /* Don't ever attempt autocompletion with Android virtual - keyboards, as those report all `event.key`s as - `Unidentified` (on Chrome) or `Process` (on Firefox) with - `event.code` 229 and thus we have no way to tell if a text - is entered or deleted. See this WONTFIX bug for details: - https://bugs.chromium.org/p/chromium/issues/detail?id=118639 - Couldn't find any similar bugreport for Firefox, but I - assume the virtual keyboard is to blame. - - An alternative is to hook into inputEvent, which has the - data, but ... there's more cursed issues right after that: - - - setSelectionRange() in Chrome on Android only renders - stuff, but doesn't actually act as such. Pressing - Backspace will only remove the highlight, but the text - stays here. Only delay-calling it through a timeout will - work as intended. Possibly related SO suggestion (back - then not even the rendering worked properly): - https://stackoverflow.com/a/13235951 - Possibly related Chrome bug: - https://bugs.chromium.org/p/chromium/issues/detail?id=32865 - - - On Firefox Mobile, programmatically changing an input - value (for the autocompletion highlight) will trigger an - input event, leading to search *and* autocompletion being - triggered again. Ultimately that results in newly typed - characters not replacing the autocompletion but rather - inserting before it, corrupting the searched string. This - event has to be explicitly ignored. - - - On Firefox Mobile, deleting a highlight with the - backspace key will result in *three* input events instead - of one: - 1. `deleteContentBackward` removing the selection (same - as Chrome or desktop Firefox) - 2. `deleteContentBackward` removing *the whole word* - that contained the selection (or the whole text if - it's just one word) - 3. `insertCompositionText`, adding the word back in, - resulting in the same state as (1). - I have no idea WHY it has to do this (possibly some - REALLY NASTY workaround to trigger correct font shaping?) - but ultimately it results in the autocompletion being - added again right after it got deleted, making this whole - thing VERY annoying to use. - - I attempted to work around the above, but it resulted in a - huge amount of browser-specific code that achieves only 90% - of the goal, with certain corner cases still being rather - broken (such as autocompletion randomly triggering when - erasing the text, even though it shouldn't). So disabling - autocompletion on this HELLISH BROKEN PLATFORM is the best - option at the moment. */ - && event.key != 'Unidentified' && event.key != 'Process' - ) { + } else if(event.key != 'Backspace' && event.key != 'Delete' && !event.metaKey && (!event.ctrlKey || event.altKey)) { Search.autocompleteNextInputEvent = true; /* Otherwise reset the flag, because when the user would press e.g. the 'a' key and then e.g. ArrowRight (which doesn't trigger From d8fc5dfe724483baabafe8dbd493a1e8d56b5a42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 5 Jan 2022 18:09:40 +0100 Subject: [PATCH 2/2] [wip] documentation: fix search autocompletion on Firefox Mobile. FFS. FFS. TODO: this is a huge piece of shit code added all over the place AND YET it still doesn't work as intended. Giving up. --- documentation/search.js | 85 ++++++++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 14 deletions(-) diff --git a/documentation/search.js b/documentation/search.js index 694bca7b..919afecd 100644 --- a/documentation/search.js +++ b/documentation/search.js @@ -52,14 +52,46 @@ var Search = { onkeypress event and reset after each oninput event. */ autocompleteNextInputEvent: false, /* On Android using a virtual keyboard, all `event.key`s are reported as - `Unidentified` with `event.code` 229 and thus the onkeydown / onKeyup - events are useless. See this WONTFIX bug for details: + `Unidentified` (in Chrome) or `Process` (in Firefox) with `event.code` + 229 and thus the onkeydown / onKeyup events are useless. See this + WONTFIX bug for details: https://bugs.chromium.org/p/chromium/issues/detail?id=118639 + Couldn't find any similar bugreport for Firefox, but I assume the + virtual keyboard is the main thing to blame, not the browser. + Instead, to make the autocompletion working when it should and not working when it shouldn't, when such event is detected we defer the handling to the input event, which is fired right after and has the data we need. */ fallbackBuggyAndroidToNextInputEvent: false, + /* On Android with Firefox Mobile, programmatically setting an input value + will trigger an inputEvent as well, triggering another round of search + and autocompletion. This variable is thus set after the input value is + modified to prevent that -- otherwise characters added with an + autocompletion highlight active will not replace the selection, but + rather insert before it, breaking the search. */ + // TODO this might be also what fixes the similar Chrome bug I couldn't + // reproduce? + nextInputEventIsFromAutocompletion: false, + /* And finally, on Android with Firefox Mobile, if there's an + autocompletion highlight and the whole selection gets deleted, it + results in THREE input events instead of one: + + 1. `deleteContentBackward` removing the selection (same as Chrome or + desktop Firefox) + 2. `deleteContentBackward` removing *the whole word* that contained the + selection (or the whole text if it's just one word) + 3. `insertCompositionText`, adding the word back in, resulting in the + same state as (1). + + This variable thus remembers what was the last input value after every + input event that adds something, and triggers autocompletion only + if the new value is different from that. + */ + // TODO but the two extra events break the `autocompleteNextInputEvent`, + // meaning autocompletion gets randomly triggered even when we're deleting + // the text, which we wanted to prevent + autocompleteNextInputEventOnlyIfNotEqualTo: '', init: function(buffer, maxResults) { let view = new DataView(buffer); @@ -544,15 +576,25 @@ var Search = { if(this.autocompleteNextInputEvent && resultsSuggestedTabAutocompletion[1].length && searchInput.selectionEnd == searchInput.value.length) { let suggestedTabAutocompletion = this.fromUtf8(resultsSuggestedTabAutocompletion[1]); - /* Delay-set the selected autocompletion text. Witout this we - hit a NASTY bug on Android Chrome where the selection is - displayed but deleting it via the virtual keyboard does - nothing. */ - window.setTimeout(function() { - let lengthBefore = searchInput.value.length; - searchInput.value += suggestedTabAutocompletion; - searchInput.setSelectionRange(lengthBefore, searchInput.value.length); - }); + if(this.autocompleteNextInputEventIfNotEqualTo != searchInput.value) { + this.autocompleteNextInputEventIfNotEqualTo = searchInput.value; + /* On Firefox, the searchInput.value modification below + will trigger an input event and thus another round of + autocompletion, resulting in new characters being added + before the autocompleted selection instead of replacing + the selection. Ignore that event. */ + this.nextInputEventIsFromAutocompletion = true; + + /* Delay-set the selected autocompletion text. Witout this we + hit a NASTY bug on Android Chrome where the selection is + displayed but deleting it via the virtual keyboard does + nothing. */ + window.setTimeout(function() { + console.log("autocompleting!!!", Search.autocompleteNextInputEventIfNotEqualTo, searchInput.value); + searchInput.value += suggestedTabAutocompletion; + searchInput.setSelectionRange(searchInput.value.length - suggestedTabAutocompletion.length, searchInput.value.length); + }); + } } /* Nothing found */ @@ -662,6 +704,14 @@ function copyToKeyboard(text) { work is beyond me. */ /* istanbul ignore if */ if(typeof document !== 'undefined') { document.getElementById('search-input').oninput = function(event) { + /* This input event was triggered as a result of adding the + autocompletion highlight on Firefox Mobile. Ignore that. */ + if(Search.nextInputEventIsFromAutocompletion) { + console.log("autocompletion-triggered search ignored!!!"); + Search.nextInputEventIsFromAutocompletion = false; + return; + } + /* Figure out if to autocomplete on Androids using a virtual keyboard, as the onkeydown event fired just before was useless. See the note about `Unidentified` keys below. */ @@ -673,6 +723,8 @@ if(typeof document !== 'undefined') { Search.fallbackBuggyAndroidToNextInputEvent = false; } + console.log("searching!!!", event, document.getElementById('search-input').value, document.getElementById('search-input').selectionEnd, document.getElementById('search-input').selectionStart, Search.autocompleteNextInputEventIfNotEqualTo); + Search.searchAndRender(document.getElementById('search-input').value); }; @@ -680,14 +732,19 @@ if(typeof document !== 'undefined') { /* Search shown */ if(window.location.hash == '#search') { /* We're on Android and using a virtual keyboard, which reports all - `event.key`s as `Unidentified` with `event.code` 229 and thus we - have no way to do anything. See this WONTFIX bug for details: + `event.key`s as `Unidentified` (on Chrome) or `Process` (on + Firefox) with `event.code` 229 and thus we have no way to do + anything. See this WONTFIX bug for details: https://bugs.chromium.org/p/chromium/issues/detail?id=118639 + Couldn't find any similar bugreport for Firefox, but I assume + the virtual keyboard is the main thing to blame, not the + browser. + We don't expect the virtual keyboard to be used for stuff like moving up/down in the result list or copying links, so the only thing to be handled there is the autocompletion enable/disable, which we'll defer to the immediately following input event. */ - if(event.key == 'Unidentified') { + if(event.key == 'Unidentified' || event.key == 'Process') { Search.fallbackBuggyAndroidToNextInputEvent = true; /* Close the search */