diff --git a/android/.gitignore b/android/.gitignore index a5720bccc7..4f0c3bb501 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -37,6 +37,8 @@ KMEA/**/assets/keymanandroid.js KMEA/**/assets/keyman.js.map KMEA/**/assets/keymanweb-webview.js KMEA/**/assets/keymanweb-webview.js.map +KMEA/**/assets/keymanweb-webview.es5.js +KMEA/**/assets/keymanweb-webview.es5.js.map KMEA/**/assets/map-polyfill.js KMEA/**/assets/sentry.min.js KMEA/**/assets/keyman-sentry.js diff --git a/android/KMEA/app/src/main/assets/keyboard.es5.html b/android/KMEA/app/src/main/assets/keyboard.es5.html new file mode 100644 index 0000000000..6384235456 --- /dev/null +++ b/android/KMEA/app/src/main/assets/keyboard.es5.html @@ -0,0 +1,30 @@ + + + + + + + Keyman + + + + + + + + + + + + + diff --git a/android/KMEA/app/src/main/assets/keyboard.html b/android/KMEA/app/src/main/assets/keyboard.html index 9473d52d5e..135081d9b7 100644 --- a/android/KMEA/app/src/main/assets/keyboard.html +++ b/android/KMEA/app/src/main/assets/keyboard.html @@ -7,16 +7,6 @@ Keyman - - - - diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index 11dec8a03f..9229884084 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -288,8 +288,11 @@ public final class KMManager { // Keyman files protected static final String KMFilename_KeyboardHtml = "keyboard.html"; + protected static final String KMFilename_KeyboardHtml_Legacy = "keyboard.es5.html"; protected static final String KMFilename_JSEngine = "keymanweb-webview.js"; protected static final String KMFilename_JSEngine_Sourcemap = "keymanweb-webview.js.map"; + protected static final String KMFilename_JSLegacyEngine = "keymanweb-webview.es5.js"; + protected static final String KMFilename_JSLegacyEngine_Sourcemap = "keymanweb-webview.es5.js.map"; protected static final String KMFilename_JSSentry = "sentry.min.js"; protected static final String KMFilename_JSSentryInit = "keyman-sentry.js"; protected static final String KMFilename_AndroidHost = "android-host.js"; @@ -849,25 +852,47 @@ public final class KMManager { private static void copyAssets(Context context) { AssetManager assetManager = context.getAssets(); + + // Will build a temp WebView in order to check Chrome version internally. + boolean legacyMode = WebViewUtils.getEngineWebViewVersionStatus(context, null, null) != WebViewUtils.EngineWebViewVersionStatus.FULL; + try { // Copy KMW files - copyAsset(context, KMFilename_KeyboardHtml, "", true); - copyAsset(context, KMFilename_JSEngine, "", true); + if(legacyMode) { + // Replaces the standard ES6-friendly version of the host page with a legacy one that + // includes polyfill requests and that links the legacy, ES5-compatible version of KMW. + copyAssetWithRename(context, KMFilename_KeyboardHtml_Legacy, KMFilename_KeyboardHtml, "", true); + + copyAsset(context, KMFilename_JSLegacyEngine, "", true); + if (KMManager.isDebugMode()) { + copyAsset(context, KMFilename_JSLegacyEngine_Sourcemap, "", true); + } + } else { + copyAsset(context, KMFilename_KeyboardHtml, "", true); + + // For versions of Chrome with full ES6 support, we use the ES6 artifact. + copyAsset(context, KMFilename_JSEngine, "", true); + if (KMManager.isDebugMode()) { + copyAsset(context, KMFilename_JSEngine_Sourcemap, "", true); + } + } + + // Is still built targeting ES5. copyAsset(context, KMFilename_JSSentry, "", true); copyAsset(context, KMFilename_JSSentryInit, "", true); copyAsset(context, KMFilename_AndroidHost, "", true); - if(KMManager.isDebugMode()) { - copyAsset(context, KMFilename_JSEngine_Sourcemap, "", true); - } copyAsset(context, KMFilename_KmwCss, "", true); copyAsset(context, KMFilename_KmwGlobeHintCss, "", true); copyAsset(context, KMFilename_Osk_Ttf_Font, "", true); // Copy default keyboard font copyAsset(context, KMDefault_KeyboardFont, "", true); - copyAsset(context, KMFilename_JSPolyfill, "", true); - copyAsset(context, KMFilename_JSPolyfill2, "", true); - copyAsset(context, KMFilename_JSPolyfill3, "", true); + + if(legacyMode) { + copyAsset(context, KMFilename_JSPolyfill, "", true); + copyAsset(context, KMFilename_JSPolyfill2, "", true); + copyAsset(context, KMFilename_JSPolyfill3, "", true); + } // Keyboard packages directory File packagesDir = new File(getPackagesDir()); @@ -963,6 +988,10 @@ public final class KMManager { } private static int copyAsset(Context context, String filename, String directory, boolean overwrite) { + return copyAssetWithRename(context, filename, filename, directory, overwrite); + } + + private static int copyAssetWithRename(Context context, String srcName, String destName, String directory, boolean overwrite) { int result; AssetManager assetManager = context.getAssets(); try { @@ -979,9 +1008,9 @@ public final class KMManager { dirPath = getResourceRoot(); } - File file = new File(dirPath, filename); + File file = new File(dirPath, destName); if (!file.exists() || overwrite) { - InputStream inputStream = assetManager.open(directory + filename); + InputStream inputStream = assetManager.open(directory + srcName); FileOutputStream outputStream = new FileOutputStream(file); FileUtils.copy(inputStream, outputStream); diff --git a/android/KMEA/build.sh b/android/KMEA/build.sh index 9b62656cae..51140d47be 100755 --- a/android/KMEA/build.sh +++ b/android/KMEA/build.sh @@ -84,6 +84,8 @@ if builder_start_action build:engine; then echo "Copying Keyman Web artifacts" cp "$KEYMAN_WEB_ROOT/build/app/webview/$CONFIG/keymanweb-webview.js" "$ENGINE_ASSETS/keymanweb-webview.js" cp "$KEYMAN_WEB_ROOT/build/app/webview/$CONFIG/keymanweb-webview.js.map" "$ENGINE_ASSETS/keymanweb-webview.js.map" + cp "$KEYMAN_WEB_ROOT/build/app/webview/$CONFIG/keymanweb-webview.es5.js" "$ENGINE_ASSETS/keymanweb-webview.es5.js" + cp "$KEYMAN_WEB_ROOT/build/app/webview/$CONFIG/keymanweb-webview.es5.js.map" "$ENGINE_ASSETS/keymanweb-webview.es5.js.map" cp "$KEYMAN_WEB_ROOT/build/app/webview/$CONFIG/map-polyfill.js" "$ENGINE_ASSETS/map-polyfill.js" cp "$KEYMAN_WEB_ROOT/build/app/resources/osk/ajax-loader.gif" "$ENGINE_ASSETS/ajax-loader.gif" cp "$KEYMAN_WEB_ROOT/build/app/resources/osk/kmwosk.css" "$ENGINE_ASSETS/kmwosk.css" diff --git a/common/models/templates/src/index.ts b/common/models/templates/src/index.ts index f828066846..52650ba9bb 100644 --- a/common/models/templates/src/index.ts +++ b/common/models/templates/src/index.ts @@ -1,22 +1,8 @@ -import { +export { SENTINEL_CODE_UNIT, applyTransform, buildMergedTransform, isHighSurrogate, isLowSurrogate, isSentinel, transformToSuggestion, defaultApplyCasing } from "./common.js"; -import PriorityQueue, { Comparator } from "./priority-queue.js"; -import QuoteBehavior from "./quote-behavior.js"; -import { Tokenization, tokenize, getLastPreCaretToken, wordbreak } from "./tokenization.js"; -import TrieModel, { TrieModelOptions } from "./trie-model.js"; - -import { extendString } from "@keymanapp/web-utils"; - -// This package requires our string-extension functions. -extendString(); - -export { - SENTINEL_CODE_UNIT, applyTransform, buildMergedTransform, isHighSurrogate, isLowSurrogate, isSentinel, - transformToSuggestion, defaultApplyCasing, // "common.ts" - PriorityQueue, Comparator, // "priority-queue.ts" - QuoteBehavior, // "quote-behavior.ts", - Tokenization, tokenize, getLastPreCaretToken, wordbreak, // "tokenization.ts" - TrieModel, TrieModelOptions // "trie-model.ts" -}; +export { default as PriorityQueue, Comparator } from "./priority-queue.js"; +export { default as QuoteBehavior } from "./quote-behavior.js"; +export { Tokenization, tokenize, getLastPreCaretToken, wordbreak } from "./tokenization.js"; +export { default as TrieModel, TrieModelOptions } from "./trie-model.js"; \ No newline at end of file diff --git a/common/models/templates/src/trie-model.ts b/common/models/templates/src/trie-model.ts index 990ab61dc6..0a571517c7 100644 --- a/common/models/templates/src/trie-model.ts +++ b/common/models/templates/src/trie-model.ts @@ -33,6 +33,8 @@ import { applyTransform, isHighSurrogate, isSentinel, SENTINEL_CODE_UNIT, transf import { getLastPreCaretToken } from "./tokenization.js"; import PriorityQueue from "./priority-queue.js"; +extendString(); + /** * @file trie-model.ts * @@ -81,6 +83,114 @@ type TextWithProbability = { p: number; // real-number weight, from 0 to 1 } +class Traversal implements LexiconTraversal { + /** + * The lexical prefix corresponding to the current traversal state. + */ + prefix: String; + + /** + * The current traversal node. Serves as the 'root' of its own sub-Trie, + * and we cannot navigate back to its parent. + */ + root: Node; + + constructor(root: Node, prefix: string) { + this.root = root; + this.prefix = prefix; + } + + *children(): Generator<{char: string, traversal: () => LexiconTraversal}> { + let root = this.root; + + if(root.type == 'internal') { + for(let entry of root.values) { + let entryNode = root.children[entry]; + + // UTF-16 astral plane check. + if(isHighSurrogate(entry)) { + // First code unit of a UTF-16 code point. + // For now, we'll just assume the second always completes such a char. + // + // Note: Things get nasty here if this is only sometimes true; in the future, + // we should compile-time enforce that this assumption is always true if possible. + if(entryNode.type == 'internal') { + let internalNode = entryNode; + for(let lowSurrogate of internalNode.values) { + let prefix = this.prefix + entry + lowSurrogate; + yield { + char: entry + lowSurrogate, + traversal: function() { return new Traversal(internalNode.children[lowSurrogate], prefix) } + } + } + } else { + // Determine how much of the 'leaf' entry has no Trie nodes, emulate them. + let fullText = entryNode.entries[0].key; + entry = entry + fullText[this.prefix.length + 1]; // The other half of the non-BMP char. + let prefix = this.prefix + entry; + + yield { + char: entry, + traversal: function () {return new Traversal(entryNode, prefix)} + } + } + } else if(isSentinel(entry)) { + continue; + } else if(!entry) { + // Prevent any accidental 'null' or 'undefined' entries from having an effect. + continue; + } else { + let prefix = this.prefix + entry; + yield { + char: entry, + traversal: function() { return new Traversal(entryNode, prefix)} + } + } + } + + return; + } else { // type == 'leaf' + let prefix = this.prefix; + + let children = root.entries.filter(function(entry) { + return entry.key != prefix && prefix.length < entry.key.length; + }) + + for(let {key} of children) { + let nodeKey = key[prefix.length]; + + if(isHighSurrogate(nodeKey)) { + // Merge the other half of an SMP char in! + nodeKey = nodeKey + key[prefix.length+1]; + } + yield { + char: nodeKey, + traversal: function() { return new Traversal(root, prefix + nodeKey)} + } + }; + return; + } + } + + get entries(): string[] { + if(this.root.type == 'leaf') { + let prefix = this.prefix; + let matches = this.root.entries.filter(function(entry) { + return entry.key == prefix; + }); + + return matches.map(function(value) { return value.content }); + } else { + let matchingLeaf = this.root.children[SENTINEL_CODE_UNIT]; + if(matchingLeaf && matchingLeaf.type == 'leaf') { + return matchingLeaf.entries.map(function(value) { return value.content }); + } else { + return []; + } + } + } +} + /** * @class TrieModel * @@ -176,115 +286,7 @@ export default class TrieModel implements LexicalModel { } public traverseFromRoot(): LexiconTraversal { - return new TrieModel.Traversal(this._trie['root'], ''); - } - - private static Traversal = class implements LexiconTraversal { - /** - * The lexical prefix corresponding to the current traversal state. - */ - prefix: String; - - /** - * The current traversal node. Serves as the 'root' of its own sub-Trie, - * and we cannot navigate back to its parent. - */ - root: Node; - - constructor(root: Node, prefix: string) { - this.root = root; - this.prefix = prefix; - } - - *children(): Generator<{char: string, traversal: () => LexiconTraversal}> { - let root = this.root; - - if(root.type == 'internal') { - for(let entry of root.values) { - let entryNode = root.children[entry]; - - // UTF-16 astral plane check. - if(isHighSurrogate(entry)) { - // First code unit of a UTF-16 code point. - // For now, we'll just assume the second always completes such a char. - // - // Note: Things get nasty here if this is only sometimes true; in the future, - // we should compile-time enforce that this assumption is always true if possible. - if(entryNode.type == 'internal') { - let internalNode = entryNode; - for(let lowSurrogate of internalNode.values) { - let prefix = this.prefix + entry + lowSurrogate; - yield { - char: entry + lowSurrogate, - traversal: function() { return new TrieModel.Traversal(internalNode.children[lowSurrogate], prefix) } - } - } - } else { - // Determine how much of the 'leaf' entry has no Trie nodes, emulate them. - let fullText = entryNode.entries[0].key; - entry = entry + fullText[this.prefix.length + 1]; // The other half of the non-BMP char. - let prefix = this.prefix + entry; - - yield { - char: entry, - traversal: function () {return new TrieModel.Traversal(entryNode, prefix)} - } - } - } else if(isSentinel(entry)) { - continue; - } else if(!entry) { - // Prevent any accidental 'null' or 'undefined' entries from having an effect. - continue; - } else { - let prefix = this.prefix + entry; - yield { - char: entry, - traversal: function() { return new TrieModel.Traversal(entryNode, prefix)} - } - } - } - - return; - } else { // type == 'leaf' - let prefix = this.prefix; - - let children = root.entries.filter(function(entry) { - return entry.key != prefix && prefix.length < entry.key.length; - }) - - for(let {key} of children) { - let nodeKey = key[prefix.length]; - - if(isHighSurrogate(nodeKey)) { - // Merge the other half of an SMP char in! - nodeKey = nodeKey + key[prefix.length+1]; - } - yield { - char: nodeKey, - traversal: function() { return new TrieModel.Traversal(root, prefix + nodeKey)} - } - }; - return; - } - } - - get entries(): string[] { - if(this.root.type == 'leaf') { - let prefix = this.prefix; - let matches = this.root.entries.filter(function(entry) { - return entry.key == prefix; - }); - - return matches.map(function(value) { return value.content }); - } else { - let matchingLeaf = this.root.children[SENTINEL_CODE_UNIT]; - if(matchingLeaf && matchingLeaf.type == 'leaf') { - return matchingLeaf.entries.map(function(value) { return value.content }); - } else { - return []; - } - } - } + return new Traversal(this._trie['root'], ''); } }; diff --git a/common/web/lm-worker/build.sh b/common/web/lm-worker/build.sh index 3732e41fd6..b7b5007161 100755 --- a/common/web/lm-worker/build.sh +++ b/common/web/lm-worker/build.sh @@ -105,6 +105,6 @@ function do_test() { } builder_run_action configure verify_npm_setup -builder_run_action clean rm -rf build/ +builder_run_action clean rm -rf build/ intermediate/ builder_run_action build do_build builder_run_action test do_test \ No newline at end of file diff --git a/common/web/utils/package.json b/common/web/utils/package.json index 1f73e92556..60bd928404 100644 --- a/common/web/utils/package.json +++ b/common/web/utils/package.json @@ -36,5 +36,8 @@ "type": "module", "paths": { "@keymanapp/keyman-version": "*" - } + }, + "sideEffects": [ + "./src/kmwstring.ts", "./build/obj/kmwstring.js" + ] } diff --git a/web/src/app/webview/build.sh b/web/src/app/webview/build.sh index d1fa445284..4e6c7a6440 100755 --- a/web/src/app/webview/build.sh +++ b/web/src/app/webview/build.sh @@ -44,16 +44,29 @@ compile_and_copy() { compile $SUBPROJECT_NAME BUILD_ROOT="${KEYMAN_ROOT}/web/build/app/webview" + SRC_ROOT="${KEYMAN_ROOT}/web/src/app/webview/src" $BUNDLE_CMD "${BUILD_ROOT}/obj/debug-main.js" \ - --out "${BUILD_ROOT}/debug/keymanweb-webview.js" \ + --out "${BUILD_ROOT}/debug/keymanweb-webview.es5.js" \ --sourceRoot "@keymanapp/keyman/web/build/app/webview/debug" $BUNDLE_CMD "${BUILD_ROOT}/obj/release-main.js" \ + --out "${BUILD_ROOT}/release/keymanweb-webview.es5.js" \ + --profile "${BUILD_ROOT}/filesize-profile.es5.log" \ + --sourceRoot "@keymanapp/keyman/web/build/app/webview/release" \ + --minify + + $BUNDLE_CMD "${SRC_ROOT}/debug-main.js" \ + --out "${BUILD_ROOT}/debug/keymanweb-webview.js" \ + --sourceRoot "@keymanapp/keyman/web/build/app/webview/debug" \ + --target "es6" + + $BUNDLE_CMD "${SRC_ROOT}/release-main.js" \ --out "${BUILD_ROOT}/release/keymanweb-webview.js" \ --profile "${BUILD_ROOT}/filesize-profile.log" \ --sourceRoot "@keymanapp/keyman/web/build/app/webview/release" \ - --minify + --minify \ + --target "es6" mkdir -p "$KEYMAN_ROOT/web/build/app/resources/osk" cp -R "$KEYMAN_ROOT/web/src/resources/osk/." "$KEYMAN_ROOT/web/build/app/resources/osk/" diff --git a/web/src/engine/osk/src/views/activator.ts b/web/src/engine/osk/src/views/activator.ts index e2484ca6ba..d2b9ec33f5 100644 --- a/web/src/engine/osk/src/views/activator.ts +++ b/web/src/engine/osk/src/views/activator.ts @@ -34,6 +34,10 @@ export class StaticActivator extends Activator { return true; } + set enabled(value: boolean) { + // does nothing; it's static. + } + get activate(): boolean { return true; }