From e0e8f6b250acb8b721b81fc6e76223fa670b1c2b Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Fri, 12 Apr 2024 08:03:26 +0700 Subject: [PATCH 01/37] chore(ios): enables debugging of keyboard WebView --- .../Classes/Keyboard/KeymanWebViewController.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift index 1bf54832f08..c82108ab8f1 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift @@ -130,6 +130,9 @@ class KeymanWebViewController: UIViewController { webView!.backgroundColor = UIColor.clear webView!.navigationDelegate = self webView!.scrollView.isScrollEnabled = false + if #available(iOSApplicationExtension 16.4, *) { + webView!.isInspectable = true + } // Disable WKWebView default layout-constraint manipulations. We ensure // safe-area boundaries are respected via InputView / InputViewController From 5836304fc3a5d56d07aec03b9eff50a9fe9da28f Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Fri, 12 Apr 2024 08:15:25 +0700 Subject: [PATCH 02/37] change(ios): iOS app check for non-stable version to enable inspectability --- .../Classes/Keyboard/KeymanWebViewController.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift index c82108ab8f1..919ec96ed16 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeymanWebViewController.swift @@ -130,8 +130,11 @@ class KeymanWebViewController: UIViewController { webView!.backgroundColor = UIColor.clear webView!.navigationDelegate = self webView!.scrollView.isScrollEnabled = false + if #available(iOSApplicationExtension 16.4, *) { - webView!.isInspectable = true + if(Version.current.tier != .stable) { + webView!.isInspectable = true + } } // Disable WKWebView default layout-constraint manipulations. We ensure From 5a0e66ce656955d2e1beabc57ae078c9d25780b9 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 15 May 2024 12:12:58 +0700 Subject: [PATCH 03/37] chore(web): adds retry mechanism for build script npm ci calls --- resources/shellHelperFunctions.sh | 43 +++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/resources/shellHelperFunctions.sh b/resources/shellHelperFunctions.sh index 3a05faaa8b0..6f75182339d 100755 --- a/resources/shellHelperFunctions.sh +++ b/resources/shellHelperFunctions.sh @@ -178,11 +178,54 @@ set_npm_version () { npm version --allow-same-version --no-git-tag-version --no-commit-hooks "$VERSION_WITH_TAG" } +NUM_RE='^[0-9]+$' + +RETRY_MAX=5 + +# Configuration: wait between 10 sec and 120 sec. + +# in seconds. +RETRY_MAX_WAIT_RANGE=111 +# in seconds +RETRY_MIN_WAIT=10 + +# $1 The current retry count (optional - defaults to 0 if not a non-negative whole number) +# $2+ (everything else) the command to retry should it fail +reattempt_if_failing ( ) { + local retryCount=$1 + + if ! [[ "$retryCount" =~ $NUM_RE ]]; then + retryCount=0 + else + shift + fi + + if [[ "$retryCount" -eq "$RETRY_MAX" ]]; then + builder_die "Retry limit of $RETRY_MAX attempts reached." + fi + + retryCount=$(( $retryCount + 1 )) + + if [[ "$retryCount" -ne "1" ]]; then + local wait_length=$(( RANDOM % RETRY_MAX_WAIT_RANGE + RETRY_MIN_WAIT )) + echo "Delaying $wait_length seconds before attempt $retryCount: \`$@\`" + sleep $wait_length + fi + + if ! "${@:1}"; then + reattempt_if_failing $retryCount "$@" + fi +} + # # Verifies that node is installed, and installs npm packages, but only once per # build invocation # verify_npm_setup() { + reattempt_if_failing _verify_npm_setup +} + +_verify_npm_setup() { # We'll piggy-back on the builder module dependency build state to determine # if npm ci has been called in the current script invocation. Adding the # prefix /external/ to module name in order to differentiate between this and From 470588933eaa4af93dbe32072c72737bb9bd1d36 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 15 May 2024 14:26:27 +0700 Subject: [PATCH 04/37] chore(common): adjustments per code review --- resources/shellHelperFunctions.sh | 46 +++++++++++++++++-------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/resources/shellHelperFunctions.sh b/resources/shellHelperFunctions.sh index 6f75182339d..a175c3e4071 100755 --- a/resources/shellHelperFunctions.sh +++ b/resources/shellHelperFunctions.sh @@ -178,42 +178,48 @@ set_npm_version () { npm version --allow-same-version --no-git-tag-version --no-commit-hooks "$VERSION_WITH_TAG" } -NUM_RE='^[0-9]+$' +# +# Re-runs the specified command-line instruction up to 5 times should it fail, waiting a +# random delay between each attempt. No re-runs are attempted after successful commands. +# +# ### Usage +# try_multiple_times "$@" +# +# ### Parameters +# 1: $@ command-line arguments +try_multiple_times ( ) { + _try_multiple_times 0 "$@" +} -RETRY_MAX=5 +# $1 The current retry count +# $2+ (everything else) the command to retry should it fail +_try_multiple_times ( ) { + local RETRY_MAX=5 -# Configuration: wait between 10 sec and 120 sec. + # Configuration: wait between 10 sec and 120 sec. -# in seconds. -RETRY_MAX_WAIT_RANGE=111 -# in seconds -RETRY_MIN_WAIT=10 + # in seconds. + local RETRY_MAX_WAIT_RANGE=111 + # in seconds + local RETRY_MIN_WAIT=10 -# $1 The current retry count (optional - defaults to 0 if not a non-negative whole number) -# $2+ (everything else) the command to retry should it fail -reattempt_if_failing ( ) { local retryCount=$1 + shift - if ! [[ "$retryCount" =~ $NUM_RE ]]; then - retryCount=0 - else - shift - fi - - if [[ "$retryCount" -eq "$RETRY_MAX" ]]; then + if (( "$retryCount" == "$RETRY_MAX" )); then builder_die "Retry limit of $RETRY_MAX attempts reached." fi retryCount=$(( $retryCount + 1 )) - if [[ "$retryCount" -ne "1" ]]; then + if (( $retryCount != 1 )); then local wait_length=$(( RANDOM % RETRY_MAX_WAIT_RANGE + RETRY_MIN_WAIT )) echo "Delaying $wait_length seconds before attempt $retryCount: \`$@\`" sleep $wait_length fi if ! "${@:1}"; then - reattempt_if_failing $retryCount "$@" + _try_multiple_times $retryCount "$@" fi } @@ -222,7 +228,7 @@ reattempt_if_failing ( ) { # build invocation # verify_npm_setup() { - reattempt_if_failing _verify_npm_setup + try_multiple_times _verify_npm_setup } _verify_npm_setup() { From e382dddb5b7b5b7fd6605640a342c6afbb6b9a1e Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Thu, 16 May 2024 16:03:14 +0700 Subject: [PATCH 05/37] chore(common): Apply suggestions from code review Co-authored-by: Marc Durdin --- resources/shellHelperFunctions.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/shellHelperFunctions.sh b/resources/shellHelperFunctions.sh index a175c3e4071..6bdec897d4b 100755 --- a/resources/shellHelperFunctions.sh +++ b/resources/shellHelperFunctions.sh @@ -183,7 +183,7 @@ set_npm_version () { # random delay between each attempt. No re-runs are attempted after successful commands. # # ### Usage -# try_multiple_times "$@" +# try_multiple_times command [param1 param2...] # # ### Parameters # 1: $@ command-line arguments @@ -214,7 +214,7 @@ _try_multiple_times ( ) { if (( $retryCount != 1 )); then local wait_length=$(( RANDOM % RETRY_MAX_WAIT_RANGE + RETRY_MIN_WAIT )) - echo "Delaying $wait_length seconds before attempt $retryCount: \`$@\`" + builder_echo "Delaying $wait_length seconds before attempt $retryCount: \`$@\`" sleep $wait_length fi From dfc90de81d1b72b3faef25854b3735113c3aff94 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 28 May 2024 13:53:04 +0700 Subject: [PATCH 06/37] fix(developer): handle editor initializing after debugger when setting execution point Fixes: #11586 Fixes: KEYMAN-DEVELOPER-1K7 --- developer/src/tike/xml/app/editor/editor.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/developer/src/tike/xml/app/editor/editor.js b/developer/src/tike/xml/app/editor/editor.js index d8a076def4c..73c48a2237f 100644 --- a/developer/src/tike/xml/app/editor/editor.js +++ b/developer/src/tike/xml/app/editor/editor.js @@ -199,6 +199,13 @@ async function loadSettings() { }; context.updateExecutionPoint = function (row) { + if(!editor) { + // At debugger startup time, it initializes asynchronously at the same + // time as the editor, which means that in some circumstances the + // debugger will call to SetExecutionPointLine before the editor has + // loaded. The safest thing to do here is just exit. #11586 + return; + } if(row >= 0) { executionPoint = editor.deltaDecorations( executionPoint, From c034bcab38a2d3a5fa228f53b4e3e8ec95687b1e Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 31 May 2024 12:47:04 +0700 Subject: [PATCH 07/37] feat(android): add KMLog.LogBreadcrumb --- .../java/com/keyman/engine/util/KMLog.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/util/KMLog.java b/android/KMEA/app/src/main/java/com/keyman/engine/util/KMLog.java index 3eeabdcbdfd..218f0af8ff9 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/util/KMLog.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/util/KMLog.java @@ -13,6 +13,7 @@ import com.keyman.engine.util.DependencyUtil; import com.keyman.engine.util.DependencyUtil.LibraryType; +import io.sentry.Breadcrumb; import io.sentry.Sentry; import io.sentry.SentryLevel; @@ -34,6 +35,44 @@ public static void LogInfo(String tag, String msg) { } } + /** + * Utility to log info and add as a Sentry breadcrumb, rather than + * as an independent message + * @param tag String of the caller + * @param msg String of the info message + */ + public static void LogBreadcrumb(String tag, String msg, boolean addStackTrace) { + if (msg != null && !msg.isEmpty()) { + Log.i(tag, msg); + + if (DependencyUtil.libraryExists(LibraryType.SENTRY) && Sentry.isEnabled()) { + Breadcrumb crumb = new Breadcrumb(); + crumb.setMessage(msg); + crumb.setLevel(SentryLevel.INFO); + + if(addStackTrace) { + StackTraceElement[] rawTrace = Thread.currentThread().getStackTrace(); + + // The call that gets us the stack-trace above... shows up in the + // stack trace, so we'll skip the first few (redundant) entries. + int skipCount = 3; + + // Sentry does limit the size of messages... so let's just + // keep 10 entries and call it a day. + int limit = Math.min(rawTrace.length, 10 + skipCount); + if(rawTrace.length > skipCount) { + String[] trace = new String[limit - skipCount]; + for (int i = skipCount; i < limit; i++) { + trace[i-skipCount] = rawTrace[i].toString(); + } + crumb.setData("stacktrace", trace); + } + } + Sentry.addBreadcrumb(crumb); + } + } + } + /** * Utility to log error and send to Sentry * @param tag String of the caller From ae0d96f504b214758831e7ac3269a7c0ed242062 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 31 May 2024 12:50:46 +0700 Subject: [PATCH 08/37] feat(android): catch correct exception + add state-logging to help diagnose exception's impact This aims to provide info needed to resolve potential issues underlying #11561. --- .../keyman/engine/cloud/CloudDownloadMgr.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/cloud/CloudDownloadMgr.java b/android/KMEA/app/src/main/java/com/keyman/engine/cloud/CloudDownloadMgr.java index 8c0d4403cb1..8a25ab09bab 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/cloud/CloudDownloadMgr.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/cloud/CloudDownloadMgr.java @@ -40,6 +40,7 @@ private synchronized static void createInstance() { if(instance!=null) return; + KMLog.LogBreadcrumb("CloudDownloadMgr", "CloudDownloadMgr.createInstance() - first call", true); instance = new CloudDownloadMgr(); } @@ -60,6 +61,7 @@ public synchronized void initialize(Context aContext) if(isInitialized) return; try { + KMLog.LogBreadcrumb("CloudDownloadMgr", "attempting CloudDownloadMgr.initialize()", true); // Runtime-registered boradcasts receivers must specify export behavior to indicate whether // or not the receiver should be exported to all other apps on the device // https://developer.android.com/about/versions/14/behavior-changes-14#runtime-receivers-exported @@ -69,11 +71,12 @@ public synchronized void initialize(Context aContext) } else { aContext.registerReceiver(completeListener, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); } - } catch (IllegalStateException e) { + } catch (IllegalArgumentException e) { String message = "initialize error: "; KMLog.LogException(TAG, message, e); } isInitialized = true; + KMLog.LogBreadcrumb("CloudDownloadMgr", ".initialize() call complete", false); } /** @@ -82,11 +85,15 @@ public synchronized void initialize(Context aContext) */ public synchronized void shutdown(Context aContext) { - if(!isInitialized) + if(!isInitialized) { return; + } + + KMLog.LogBreadcrumb("CloudDownloadMgr", "CloudDownloadMgr.shutdown()", true); + try { aContext.unregisterReceiver(completeListener); - } catch (IllegalStateException e) { + } catch (IllegalArgumentException e) { String message = "shutdown error: "; KMLog.LogException(TAG, message, e); } @@ -206,8 +213,10 @@ public void executeAsDownload(Context aContext, String aD CloudApiTypes.CloudApiParam... params) { if(!isInitialized) { - Log.w(TAG, "Downloadmanager not initialized. Initializing CloudDownloadMgr."); + Log.w(TAG, "DownloadManager not initialized. Initializing CloudDownloadMgr."); initialize(aContext); + } else { + KMLog.LogBreadcrumb("CloudDownloadMgr", "CloudDownloadMgr.executeAsDownload() called; already initialized", true); } synchronized (downloadSetByDownloadIdentifier) { @@ -218,7 +227,7 @@ public void executeAsDownload(Context aContext, String aD DownloadManager downloadManager = (DownloadManager) aContext.getSystemService(Context.DOWNLOAD_SERVICE); if(downloadManager==null) - throw new IllegalStateException("Downloadmanager is not available"); + throw new IllegalStateException("DownloadManager is not available"); aCallback.initializeContext(aContext); From 6251faa23d4f3998aabbdd60a4e99d34d1ee97ca Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Fri, 31 May 2024 14:52:31 +0700 Subject: [PATCH 09/37] fix(common): only retry the npm ci call itself --- resources/shellHelperFunctions.sh | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/resources/shellHelperFunctions.sh b/resources/shellHelperFunctions.sh index 6bdec897d4b..6d47747304e 100755 --- a/resources/shellHelperFunctions.sh +++ b/resources/shellHelperFunctions.sh @@ -228,10 +228,6 @@ _try_multiple_times ( ) { # build invocation # verify_npm_setup() { - try_multiple_times _verify_npm_setup -} - -_verify_npm_setup() { # We'll piggy-back on the builder module dependency build state to determine # if npm ci has been called in the current script invocation. Adding the # prefix /external/ to module name in order to differentiate between this and @@ -249,6 +245,8 @@ _verify_npm_setup() { builder_die "Build environment setup error detected! Please ensure Node.js is installed!" pushd "$KEYMAN_ROOT" > /dev/null - npm ci + + try_multiple_times npm ci + popd > /dev/null -} +} \ No newline at end of file From b6a06c662627372a7b35916b93d84deb281cde8d Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Fri, 31 May 2024 15:01:33 +0700 Subject: [PATCH 10/37] change(common): simplifies retry command-execution syntax --- resources/shellHelperFunctions.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/shellHelperFunctions.sh b/resources/shellHelperFunctions.sh index 6d47747304e..3c00f0490d2 100755 --- a/resources/shellHelperFunctions.sh +++ b/resources/shellHelperFunctions.sh @@ -218,7 +218,7 @@ _try_multiple_times ( ) { sleep $wait_length fi - if ! "${@:1}"; then + if ! "$@"; then _try_multiple_times $retryCount "$@" fi } @@ -246,7 +246,7 @@ verify_npm_setup() { pushd "$KEYMAN_ROOT" > /dev/null - try_multiple_times npm ci + try_multiple_times npm install @keymanapp/totally-not-a-package-that-is-distributed-so-it-should-make-an-error popd > /dev/null } \ No newline at end of file From 66bdc29f6e92dc67e61dea5a85cf02513f0a987b Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Fri, 31 May 2024 15:08:36 +0700 Subject: [PATCH 11/37] fix(common): revert accidental test-code inclusion --- resources/shellHelperFunctions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/shellHelperFunctions.sh b/resources/shellHelperFunctions.sh index 3c00f0490d2..574cff1eff1 100755 --- a/resources/shellHelperFunctions.sh +++ b/resources/shellHelperFunctions.sh @@ -246,7 +246,7 @@ verify_npm_setup() { pushd "$KEYMAN_ROOT" > /dev/null - try_multiple_times npm install @keymanapp/totally-not-a-package-that-is-distributed-so-it-should-make-an-error + try_multiple_times npm ci popd > /dev/null } \ No newline at end of file From 8608a267016cb0c055502460858fca1d675a6c9c Mon Sep 17 00:00:00 2001 From: Nnyny Date: Mon, 3 Jun 2024 14:40:57 +0700 Subject: [PATCH 12/37] add context/character-map --- developer/src/tike/xml/help/contexthelp.xml | 84 ++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/developer/src/tike/xml/help/contexthelp.xml b/developer/src/tike/xml/help/contexthelp.xml index 3f071d11c39..d429bc80f37 100644 --- a/developer/src/tike/xml/help/contexthelp.xml +++ b/developer/src/tike/xml/help/contexthelp.xml @@ -4,11 +4,93 @@
-

The Filter allows a user to reduce the number of characters displayed in the character map. The standard filter options used are by font name or block name.

+

The Filter allows a user to reduce the number of characters displayed in the character map. The standard filter options used are by font name or block name.

+

The Character Map Grid displays each character in a square, with a visual representation of the character and the Unicode value displayed at the bottom of the square. Each font group is divided into a block headed by the font name.

+ + +

The Filter allows a user to reduce the number of characters displayed in the + character map. The standard filter options used are by font name or block name.

+
+ + +

The filter format for a range is: [U+]XXXX-[U+]YYYY, where U+ is optional, + XXXX is the starting Unicode value and YYYY is the finishing Unicode value.

+
+ + +

The filter format for a range is: [U+]XXXX-[U+]YYYY, where U+ is optional, + XXXX is the starting Unicode value and YYYY is the finishing Unicode value.

+
+ + +

The filter format for a range is: [U+]XXXX-[U+]YYYY, where U+ is optional, + XXXX is the starting Unicode value and YYYY is the finishing Unicode value.

+
+ + +

The Filter allows a user to reduce the number of characters displayed in the + character map. The standard filter options used are by font name or block name.

+
+ + +

">" placed at the start of an entry will only show characters in the currently + selected Character Map font. This is helpful when trying to determine which characters + a given font supports.



+

Example: >LAO



+

finds all characters with names starting in "LAO" in the current font

+
+ + +

"<" placed at the start of an entry will search Unicode block names instead of + character names. This is helpful when searching for characters within related + blocks



+

Example: <Thai



+

finds the Thai Unicode block

+
+ + +

Using "*" in an entry serves as a wildcard for any number of places in that entry. + For example, searching for "greek*alpha" will find characters whose Unicode names begin + with the word "Greek" and contain the word "Alpha" any number of places later. + This is helpful when searching for characters that share a common element in + their names (e.g. capital).

+
+ + +

Using "?" anywhere in an entry serves as a wildcard for that single place in the entry. + For example, searching for "s???e" will return both the SPACE and the SMILE characters, + among others.



+
+ + +

Example: 1000-119F



+

finds all characters between U+1000 and U+119F (inclusive) - + the Myanmar alphabet in this case

+
+ + +

Example: LATIN * LETTER [AEIOU]



+

finds all all Latin A,E,I,O or U vowel combinations

+
+ + +

"$" placed at the end of an entry will match from the end of a Unicode character name. + This option works best when used with "*" or "?".



+

Example: LATIN * LETTER A$



+

finds only "a" and "A"

+
+ + +

Click OK to save changes and close the dialog.

+
+ + +

Click Cancel to close the dialog without saving changes.

+