diff --git a/android/.gitignore b/android/.gitignore
index 7c298cad5c3..84e6ca56ada 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/worker-thread.js
+KMEA/**/assets/worker-thread.js.map
KMEA/**/assets/sentry.min.js
KMEA/**/assets/keyman-sentry.js
KMEA/**/assets/kmwosk.css
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 b0e8661b953..4b57a6a9bc1 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
@@ -375,6 +375,7 @@ public int toInt() {
// Keyman files
protected static final String KMFilename_KeyboardHtml = "keyboard.html";
protected static final String KMFilename_JSEngine = "keymanweb-webview.js";
+ protected static final String KMFilename_LMWorker = "worker-thread.js";
protected static final String KMFilename_JSSentry = "keyman-sentry.js";
protected static final String KMFilename_AndroidHost = "android-host.js";
protected static final String KMFilename_KmwCss = "kmwosk.css";
@@ -1073,10 +1074,12 @@ private static void copyAssets(Context context) {
AssetManager assetManager = context.getAssets();
try {
+ // TODO: these files should be in a constant array, not enumerated separately here (see also iOS)
// Copy KMW files
copyAsset(context, KMFilename_KeyboardHtml, "", true);
copyAsset(context, KMFilename_JSEngine, "", true);
+ copyAsset(context, KMFilename_LMWorker, "", true);
copyAsset(context, KMFilename_JSSentry, "", true);
copyAsset(context, KMFilename_AndroidHost, "", true);
copyAsset(context, KMFilename_KmwCss, "", true);
diff --git a/android/KMEA/build.sh b/android/KMEA/build.sh
index 14dc821f198..6c8400db423 100755
--- a/android/KMEA/build.sh
+++ b/android/KMEA/build.sh
@@ -55,6 +55,8 @@ do_build() {
builder_echo "Copying Keyman Web artifacts"
cp "$KEYMAN_ROOT/web/build/app/webview/$BUILDER_CONFIGURATION/keymanweb-webview.js" "$ENGINE_ASSETS/"
cp "$KEYMAN_ROOT/web/build/app/webview/$BUILDER_CONFIGURATION/keymanweb-webview.js.map" "$ENGINE_ASSETS/"
+ cp "$KEYMAN_ROOT/web/build/app/webview/$BUILDER_CONFIGURATION/worker-thread.js" "$ENGINE_ASSETS/"
+ cp "$KEYMAN_ROOT/web/build/app/webview/$BUILDER_CONFIGURATION/worker-thread.js.map" "$ENGINE_ASSETS/"
cp "$KEYMAN_ROOT/web/build/app/resources/osk/ajax-loader.gif" "$ENGINE_ASSETS/"
cp "$KEYMAN_ROOT/web/build/app/resources/osk/kmwosk.css" "$ENGINE_ASSETS/"
cp "$KEYMAN_ROOT/web/build/app/resources/osk/globe-hint.css" "$ENGINE_ASSETS/"
diff --git a/common/web/sentry-manager/src/index.ts b/common/web/sentry-manager/src/index.ts
index a87694e4e0e..f8e7a39eec1 100644
--- a/common/web/sentry-manager/src/index.ts
+++ b/common/web/sentry-manager/src/index.ts
@@ -18,6 +18,7 @@ export class KeymanSentryManager {
static STANDARD_ALIASABLE_FILES = {
'keymanweb.js': 'keymanweb.js',
'keymanweb-webview.js': 'keymanweb-webview.js',
+ 'worker-thread.js': 'worker-thread.js',
'kmwuibutton.js': 'kmwuibutton.js',
'kmwuifloat.js': 'kmwuifloat.js',
'kmwuitoggle.js': 'kmwuitoggle.js',
diff --git a/common/web/types/src/lexical-model-types.ts b/common/web/types/src/lexical-model-types.ts
index a3c8df1c98b..712c698b890 100644
--- a/common/web/types/src/lexical-model-types.ts
+++ b/common/web/types/src/lexical-model-types.ts
@@ -259,8 +259,8 @@ export interface Transform {
* transition event - say, the resulting effects of neighbor keys that may
* have been missed due to "fat fingering".
*
- * Also note that the Transform reference cannot be preserved across WebWorker
- * boundaries, but this ID may.
+ * Also note that the Transform reference cannot be preserved across
+ * WebPredictiveTextWorker boundaries, but this ID may.
*
* This is *separate* from any LMLayer-internal identification values.
*/
diff --git a/ios/.gitignore b/ios/.gitignore
index 556562d4b6e..8c13749b0d7 100644
--- a/ios/.gitignore
+++ b/ios/.gitignore
@@ -18,6 +18,8 @@ engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/kmwosk.css
engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/keyman.js.map
engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/keymanweb-webview.js
engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/keymanweb-webview.js.map
+engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/worker-thread.js
+engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/worker-thread.js.map
engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/keyman-sentry.js
engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/sentry.min.js
Keyman 2*
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Resource Management/Storage.swift b/ios/engine/KMEI/KeymanEngine/Classes/Resource Management/Storage.swift
index fd8b98e8a88..3c5e4f3dac5 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/Resource Management/Storage.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/Resource Management/Storage.swift
@@ -225,6 +225,7 @@ class Storage {
// MARK: - Copying
extension Storage {
func copyKMWFiles(from bundle: Bundle) throws {
+ // TODO: these should be in a constant array, not enumerated individually here (see also Android)
try Storage.copy(from: bundle,
resourceName: Resources.kmwFilename,
dstDir: baseDir,
@@ -237,6 +238,18 @@ extension Storage {
resourceName: "keymanweb-webview.js",
dstDir: baseDir,
excludeFromBackup: true)
+ try Storage.copy(from: bundle,
+ resourceName: "keymanweb-webview.js.map",
+ dstDir: baseDir,
+ excludeFromBackup: true)
+ try Storage.copy(from: bundle,
+ resourceName: "worker-thread.js",
+ dstDir: baseDir,
+ excludeFromBackup: true)
+ try Storage.copy(from: bundle,
+ resourceName: "worker-thread.js.map",
+ dstDir: baseDir,
+ excludeFromBackup: true)
try Storage.copy(from: bundle,
resourceName: "keyman-sentry.js",
dstDir: baseDir,
diff --git a/ios/engine/build.sh b/ios/engine/build.sh
index 2fa7add0828..f87fe2262fc 100755
--- a/ios/engine/build.sh
+++ b/ios/engine/build.sh
@@ -109,6 +109,9 @@ function update_bundle ( ) {
cp "$KMW_RESOURCES/osk/kmwosk.css" "$BUNDLE_PATH/kmwosk.css"
cp "$KMW_RESOURCES/osk/keymanweb-osk.ttf" "$BUNDLE_PATH/keymanweb-osk.ttf"
cp "$KMW_PRODUCT/keymanweb-webview.js" "$BUNDLE_PATH/keymanweb-webview.js"
+ cp "$KMW_PRODUCT/keymanweb-webview.js.map" "$BUNDLE_PATH/keymanweb-webview.js.map"
+ cp "$KMW_PRODUCT/worker-thread.js" "$BUNDLE_PATH/worker-thread.js"
+ cp "$KMW_PRODUCT/worker-thread.js.map" "$BUNDLE_PATH/worker-thread.js.map"
cp "$KEYMAN_ROOT/common/web/sentry-manager/build/lib/index.js" "$BUNDLE_PATH/keyman-sentry.js"
}
diff --git a/web/README.md b/web/README.md
index 2abc8f2b580..ad72d4b22f7 100644
--- a/web/README.md
+++ b/web/README.md
@@ -98,7 +98,7 @@ graph TD;
Gestures["/web/src/engine/gesture-processor"];
Gestures-->WebUtils;
- subgraph PredText["PredText: WebWorker + its interface"]
+ subgraph PredText["PredText: WebPredictiveTextWorker + its interface"]
LMLayer;
LMWorker;
Models;
diff --git a/web/src/app/browser/build.sh b/web/src/app/browser/build.sh
index 41fc9c5230b..dd72d621987 100755
--- a/web/src/app/browser/build.sh
+++ b/web/src/app/browser/build.sh
@@ -49,13 +49,13 @@ compile_and_copy() {
BUILD_ROOT="${KEYMAN_ROOT}/web/build/app/browser"
SRC_ROOT="${KEYMAN_ROOT}/web/src/app/browser/src"
- node_es_bundle "${SRC_ROOT}/debug-main.js" \
+ node_es_bundle "${SRC_ROOT}/main.js" \
--out "${BUILD_ROOT}/debug/keymanweb.js" \
--charset "utf8" \
--sourceRoot "@keymanapp/keyman/web/build/app/browser/debug" \
--target "es6"
- node_es_bundle "${SRC_ROOT}/release-main.js" \
+ node_es_bundle "${SRC_ROOT}/main.js" \
--out "${BUILD_ROOT}/release/keymanweb.js" \
--charset "utf8" \
--profile "${BUILD_ROOT}/filesize-profile.log" \
@@ -72,6 +72,14 @@ compile_and_copy() {
mkdir -p "$KEYMAN_ROOT/web/build/app/resources/osk"
cp -R "$KEYMAN_ROOT/web/src/resources/osk/." "$KEYMAN_ROOT/web/build/app/resources/osk/"
+ cp "${KEYMAN_ROOT}/web/src/engine/predictive-text/worker-thread/build/lib/worker-thread.js" "${BUILD_ROOT}/debug/worker-thread.js"
+ cp "${KEYMAN_ROOT}/web/src/engine/predictive-text/worker-thread/build/lib/worker-thread.js.map" "${BUILD_ROOT}/debug/worker-thread.js.map"
+ cp "${KEYMAN_ROOT}/web/src/engine/predictive-text/worker-thread/build/lib/worker-thread.d.ts" "${BUILD_ROOT}/debug/worker-thread.d.ts"
+
+ # TODO: rename to lm-worker.js? do we keep debug/release distinction through .js vs .min.js or via folder?
+ cp "${KEYMAN_ROOT}/web/src/engine/predictive-text/worker-thread/build/lib/worker-thread.min.js" "${BUILD_ROOT}/release/worker-thread.js"
+ cp "${KEYMAN_ROOT}/web/src/engine/predictive-text/worker-thread/build/lib/worker-thread.min.js.map" "${BUILD_ROOT}/release/worker-thread.js.map"
+
# Update the build/publish copy of our build artifacts
prepare
diff --git a/web/src/app/browser/src/debug-main.ts b/web/src/app/browser/src/main.ts
similarity index 54%
rename from web/src/app/browser/src/debug-main.ts
rename to web/src/app/browser/src/main.ts
index aa6b23a2a4b..99b57f491af 100644
--- a/web/src/app/browser/src/debug-main.ts
+++ b/web/src/app/browser/src/main.ts
@@ -1,5 +1,9 @@
+/*
+ * Keyman is copyright (C) SIL Global. MIT License.
+ */
+
import { KeymanEngine } from './keymanEngine.js'
-import { SourcemappedWorker } from '@keymanapp/lexical-model-layer/web'
+import { WebPredictiveTextWorkerFactory } from '@keymanapp/lexical-model-layer/web'
/**
* Determine path and protocol of executing script, setting them as
@@ -9,4 +13,4 @@ const ss = (document.currentScript as HTMLScriptElement)?.src;
const sPath = ss ? ss.substring(0, ss.lastIndexOf('/') + 1) : './';
// @ts-ignore
-window['keyman'] = new KeymanEngine(SourcemappedWorker, sPath);
\ No newline at end of file
+window['keyman'] = new KeymanEngine(new WebPredictiveTextWorkerFactory(sPath + 'worker-thread.js'), sPath);
\ No newline at end of file
diff --git a/web/src/app/webview/build.sh b/web/src/app/webview/build.sh
index dceaf197158..dfc338ac46e 100755
--- a/web/src/app/webview/build.sh
+++ b/web/src/app/webview/build.sh
@@ -44,13 +44,13 @@ compile_and_copy() {
BUILD_ROOT="${KEYMAN_ROOT}/web/build/app/webview"
SRC_ROOT="${KEYMAN_ROOT}/web/src/app/webview/src"
- node_es_bundle "${SRC_ROOT}/debug-main.js" \
+ node_es_bundle "${SRC_ROOT}/main.js" \
--out "${BUILD_ROOT}/debug/keymanweb-webview.js" \
--charset "utf8" \
--sourceRoot "@keymanapp/keyman/web/build/app/webview/debug" \
--target "es6"
- node_es_bundle "${SRC_ROOT}/release-main.js" \
+ node_es_bundle "${SRC_ROOT}/main.js" \
--out "${BUILD_ROOT}/release/keymanweb-webview.js" \
--charset "utf8" \
--profile "${BUILD_ROOT}/filesize-profile.log" \
@@ -83,6 +83,14 @@ compile_and_copy() {
"$script" "$sourcemap" --clean
done
+ cp "${KEYMAN_ROOT}/web/src/engine/predictive-text/worker-thread/build/lib/worker-thread.js" "${BUILD_ROOT}/debug/worker-thread.js"
+ cp "${KEYMAN_ROOT}/web/src/engine/predictive-text/worker-thread/build/lib/worker-thread.js.map" "${BUILD_ROOT}/debug/worker-thread.js.map"
+ cp "${KEYMAN_ROOT}/web/src/engine/predictive-text/worker-thread/build/lib/worker-thread.d.ts" "${BUILD_ROOT}/debug/worker-thread.d.ts"
+
+ # TODO: rename to lm-worker.js? do we keep debug/release distinction through .js vs .min.js or via folder?
+ cp "${KEYMAN_ROOT}/web/src/engine/predictive-text/worker-thread/build/lib/worker-thread.min.js" "${BUILD_ROOT}/release/worker-thread.js"
+ cp "${KEYMAN_ROOT}/web/src/engine/predictive-text/worker-thread/build/lib/worker-thread.min.js.map" "${BUILD_ROOT}/release/worker-thread.js.map"
+
# For dependent test pages.
builder_launch /web/src/test/manual/embed/android-harness/build.sh configure,build
}
diff --git a/web/src/app/webview/src/debug-main.ts b/web/src/app/webview/src/debug-main.ts
deleted file mode 100644
index aa6b23a2a4b..00000000000
--- a/web/src/app/webview/src/debug-main.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { KeymanEngine } from './keymanEngine.js'
-import { SourcemappedWorker } from '@keymanapp/lexical-model-layer/web'
-
-/**
-* Determine path and protocol of executing script, setting them as
-* construction defaults.
-*/
-const ss = (document.currentScript as HTMLScriptElement)?.src;
-const sPath = ss ? ss.substring(0, ss.lastIndexOf('/') + 1) : './';
-
-// @ts-ignore
-window['keyman'] = new KeymanEngine(SourcemappedWorker, sPath);
\ No newline at end of file
diff --git a/web/src/app/browser/src/release-main.ts b/web/src/app/webview/src/main.ts
similarity index 54%
rename from web/src/app/browser/src/release-main.ts
rename to web/src/app/webview/src/main.ts
index 3e3a168ccd9..99b57f491af 100644
--- a/web/src/app/browser/src/release-main.ts
+++ b/web/src/app/webview/src/main.ts
@@ -1,5 +1,9 @@
+/*
+ * Keyman is copyright (C) SIL Global. MIT License.
+ */
+
import { KeymanEngine } from './keymanEngine.js'
-import { WebWorker } from '@keymanapp/lexical-model-layer/web'
+import { WebPredictiveTextWorkerFactory } from '@keymanapp/lexical-model-layer/web'
/**
* Determine path and protocol of executing script, setting them as
@@ -9,4 +13,4 @@ const ss = (document.currentScript as HTMLScriptElement)?.src;
const sPath = ss ? ss.substring(0, ss.lastIndexOf('/') + 1) : './';
// @ts-ignore
-window['keyman'] = new KeymanEngine(WebWorker, sPath);
\ No newline at end of file
+window['keyman'] = new KeymanEngine(new WebPredictiveTextWorkerFactory(sPath + 'worker-thread.js'), sPath);
\ No newline at end of file
diff --git a/web/src/app/webview/src/release-main.ts b/web/src/app/webview/src/release-main.ts
deleted file mode 100644
index 3e3a168ccd9..00000000000
--- a/web/src/app/webview/src/release-main.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { KeymanEngine } from './keymanEngine.js'
-import { WebWorker } from '@keymanapp/lexical-model-layer/web'
-
-/**
-* Determine path and protocol of executing script, setting them as
-* construction defaults.
-*/
-const ss = (document.currentScript as HTMLScriptElement)?.src;
-const sPath = ss ? ss.substring(0, ss.lastIndexOf('/') + 1) : './';
-
-// @ts-ignore
-window['keyman'] = new KeymanEngine(WebWorker, sPath);
\ No newline at end of file
diff --git a/web/src/engine/build.sh b/web/src/engine/build.sh
index 70150fe681c..121819b990d 100755
--- a/web/src/engine/build.sh
+++ b/web/src/engine/build.sh
@@ -67,7 +67,7 @@ run_tests() {
# "TypeError [ERR_INVALID_URL_SCHEME]: The URL must be of scheme file"
# when c8 tries to create a report from the raw coverage data. The reason
# is a URL starting with `data:text/javascript` coming from
- # `web/src/engine/predictive-text/worker-main/src/node/mappedWorker.ts`.
+ # `web/src/engine/predictive-text/worker-main/src/node/node-predictive-text-worker.ts`.
#
# So we first run all javascript tests except languageProcessor.tests.js
# with coverage, and then in a second step run languageProcessor.tests.js
diff --git a/web/src/engine/predictive-text/build.sh b/web/src/engine/predictive-text/build.sh
index ac6d2a670b0..e63963448f8 100755
--- a/web/src/engine/predictive-text/build.sh
+++ b/web/src/engine/predictive-text/build.sh
@@ -32,7 +32,7 @@ builder_describe_outputs \
build:templates "/web/src/engine/predictive-text/templates/build/obj/index.js" \
build:wordbreakers "/web/src/engine/predictive-text/wordbreakers/build/main/obj/index.js" \
build:worker-main "/web/src/engine/predictive-text/worker-main/build/lib/index.mjs" \
- build:worker-thread "/web/src/engine/predictive-text/worker-thread/build/lib/worker-main.wrapped.js"
+ build:worker-thread "/web/src/engine/predictive-text/worker-thread/build/lib/worker-thread.js"
#### Build action definitions ####
diff --git a/web/src/engine/predictive-text/worker-main/src/index.ts b/web/src/engine/predictive-text/worker-main/src/index.ts
index bdee0489174..39eb7f39430 100644
--- a/web/src/engine/predictive-text/worker-main/src/index.ts
+++ b/web/src/engine/predictive-text/worker-main/src/index.ts
@@ -1,7 +1,8 @@
-export { LMLayer } from './lmlayer.js';
-export { NodeWorker } from './node/node-worker.js';
-export { WebWorker } from './web/web-worker.js';
+/*
+ * Keyman is copyright (C) SIL Global. MIT License.
+ */
-export interface WorkerFactory {
- constructInstance(): Worker
-}
\ No newline at end of file
+export { LMLayer } from './lmlayer.js';
+export { type WorkerFactory } from './worker-factory.js';
+export { NodePredictiveTextWorkerFactory } from './node/node-predictive-text-worker-factory.js';
+export { WebPredictiveTextWorkerFactory } from './web/web-predictive-text-worker-factory.js';
diff --git a/web/src/engine/predictive-text/worker-main/src/lmlayer.ts b/web/src/engine/predictive-text/worker-main/src/lmlayer.ts
index 46d0aa7c936..b9ea6b721f0 100644
--- a/web/src/engine/predictive-text/worker-main/src/lmlayer.ts
+++ b/web/src/engine/predictive-text/worker-main/src/lmlayer.ts
@@ -230,7 +230,7 @@ export class LMLayer {
/**
* Clears out any computational resources in use by the LMLayer, including shutting
- * down any internal WebWorkers.
+ * down any internal PredictiveTextWorkers.
*/
public shutdown() {
this._worker.terminate();
diff --git a/web/src/engine/predictive-text/worker-main/src/node/index.ts b/web/src/engine/predictive-text/worker-main/src/node/index.ts
index dc5957207b4..ede69d81a76 100644
--- a/web/src/engine/predictive-text/worker-main/src/node/index.ts
+++ b/web/src/engine/predictive-text/worker-main/src/node/index.ts
@@ -1,7 +1,7 @@
-export { LMLayer } from '../lmlayer.js';
-export { NodeWorker } from './node-worker.js';
-export { SourcemappedWorker } from './sourcemappedWorker.js';
+/*
+ * Keyman is copyright (C) SIL Global. MIT License.
+ */
-export interface WorkerFactory {
- constructInstance(): Worker
- }
\ No newline at end of file
+export { LMLayer } from '../lmlayer.js';
+export { type WorkerFactory } from '../worker-factory.js';
+export { NodePredictiveTextWorkerFactory } from './node-predictive-text-worker-factory.js';
diff --git a/web/src/engine/predictive-text/worker-main/src/node/node-predictive-text-worker-factory.ts b/web/src/engine/predictive-text/worker-main/src/node/node-predictive-text-worker-factory.ts
new file mode 100644
index 00000000000..4e92e450caf
--- /dev/null
+++ b/web/src/engine/predictive-text/worker-main/src/node/node-predictive-text-worker-factory.ts
@@ -0,0 +1,22 @@
+/*
+ * Keyman is copyright (C) SIL Global. MIT License.
+ */
+import { WorkerFactory } from "../worker-factory.js";
+import { NodePredictiveTextWorker } from "./node-predictive-text-worker.js";
+
+import * as path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __filename__ = fileURLToPath(import.meta.url);
+const __dirname__ = path.dirname(__filename__);
+export const nodeLexicalModelWorkerPath = __dirname__ + "/../../../../worker-thread/build/lib/worker-thread.js";
+
+export class NodePredictiveTextWorkerFactory implements WorkerFactory {
+ private workerFilename: string;
+ constructor(workerFilename?: string) {
+ this.workerFilename = workerFilename ?? nodeLexicalModelWorkerPath;
+ }
+ constructInstance(): Worker {
+ return new NodePredictiveTextWorker(this.workerFilename);
+ }
+}
diff --git a/web/src/engine/predictive-text/worker-main/src/node/mappedWorker.ts b/web/src/engine/predictive-text/worker-main/src/node/node-predictive-text-worker.ts
similarity index 68%
rename from web/src/engine/predictive-text/worker-main/src/node/mappedWorker.ts
rename to web/src/engine/predictive-text/worker-main/src/node/node-predictive-text-worker.ts
index 8b3d9d2b7b9..ef1928a4b2c 100644
--- a/web/src/engine/predictive-text/worker-main/src/node/mappedWorker.ts
+++ b/web/src/engine/predictive-text/worker-main/src/node/node-predictive-text-worker.ts
@@ -1,3 +1,10 @@
+/*
+ * Keyman is copyright (C) SIL Global. MIT License.
+ *
+ * NodePredictiveTextWorker mirrors the necessary interfaces for
+ * WebPredictiveTextWorker in Node. It is used only for unit tests.
+ */
+
// We use a subset of the Worker interface here; compiling directly against the true
// WebWorker type definitions would require us to implement more methods than we do.
///
@@ -7,10 +14,13 @@ import * as worker from 'node:worker_threads';
import { Buffer } from 'node:buffer';
import { URL } from 'node:url';
+
/**
* Defines mappings from Node Worker signatures to WebWorker signatures
+ *
+ * TODO: move this to a separate module, no need for it to be embedded string
*/
-const nodeWorkerToWebWorkerMappingSource = `
+const nodeWorkerToWebWorkerMappingSource = (filename: string) => (`
import { parentPort } from 'node:worker_threads';
import fs from 'node:fs';
import vm from 'node:vm';
@@ -42,37 +52,24 @@ function importScripts(...args) {
const self = globalThis;
self.postMessage = postMessage;
self.importScripts = importScripts;
-`;
+self.self = self; // make it global!
+// Start off by importing the main worker itself
+importScripts(${JSON.stringify(filename)});
+`);
/**
* Uses the Node version of Workers to provide proper, authentic separate-thread
* 'sandboxing'. Also intercepts and interprets certain WebWorker method signatures
* necessary to run the WebWorker-oriented worker code.
- *
- * Alternatively, only after writing this did I discover this package:
- * https://github.com/developit/web-worker. They also ran one notable issue I did:
- * Node 18.x, at least, does not support use of Node Blobs for construction of a
- * Worker: https://github.com/developit/web-worker/pull/32... unlike Web Workers.
- *
- * So... Base64-encoded Data URLs it is.
- *
- * What we have here is perfectly fine for now, but if we need more complicated
- * cross-platform Worker support in the future, it may be wise to swap to use of
- * that package.
*/
-export class MappedWorker extends worker.Worker implements Worker {
- constructor(scriptStr: string) {
- const concatenatedScript = `
- ${nodeWorkerToWebWorkerMappingSource}
-
- ${scriptStr}
- `;
- const buffer = Buffer.from(concatenatedScript);
+export class NodePredictiveTextWorker extends worker.Worker implements Worker {
+ constructor(workerFilename: string) {
+ const buffer = Buffer.from(nodeWorkerToWebWorkerMappingSource(workerFilename));
const dataSrc = "data:text/javascript;base64," + buffer.toString('base64');
//@ts-ignore
super(new URL(dataSrc));
- // WebWorkers have a defined `onmessage` function, rather than this.on('message', ...)
+ // Workers have a defined `onmessage` function, rather than this.on('message', ...)
this.on('message', (ev) => {
if(this.onmessage) {
this.onmessage({data: ev[0]});
diff --git a/web/src/engine/predictive-text/worker-main/src/node/node-worker.ts b/web/src/engine/predictive-text/worker-main/src/node/node-worker.ts
deleted file mode 100644
index e773ea9ffa4..00000000000
--- a/web/src/engine/predictive-text/worker-main/src/node/node-worker.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Keyman is copyright (C) SIL Global. MIT License.
- */
-import { MappedWorker } from "./mappedWorker.js";
-import { unwrap } from '../unwrap.js';
-
-import { LMLayerWorkerCode, LMLayerWorkerSourcemapComment } from "@keymanapp/lm-worker/worker-main.wrapped.min.js";
-
-export class NodeWorker {
- static constructInstance(): Worker {
- let scriptStr = unwrap(LMLayerWorkerCode);
- const srcMapUrlIndex = scriptStr.indexOf("//# sourceMappingURL")
- if(srcMapUrlIndex > 0) {
- scriptStr = scriptStr.substring(0, srcMapUrlIndex);
- }
-
- scriptStr += '\n' + LMLayerWorkerSourcemapComment;
- return new MappedWorker(scriptStr);
- }
-}
\ No newline at end of file
diff --git a/web/src/engine/predictive-text/worker-main/src/node/sourcemappedWorker.ts b/web/src/engine/predictive-text/worker-main/src/node/sourcemappedWorker.ts
deleted file mode 100644
index a8141939ce9..00000000000
--- a/web/src/engine/predictive-text/worker-main/src/node/sourcemappedWorker.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { MappedWorker } from "./mappedWorker.js";
-import { unwrap } from '../unwrap.js';
-
-import { LMLayerWorkerCode, LMLayerWorkerSourcemapComment } from "@keymanapp/lm-worker/worker-main.wrapped.js";
-
-export class SourcemappedWorker {
- static constructInstance(): Worker {
- let scriptStr = unwrap(LMLayerWorkerCode);
-
- // If this is definitively set to either true or false, tree-shaking can take effect.
- // An imported const variable doesn't seem to do it, though.
- // if(false) {
- scriptStr += '\n' + LMLayerWorkerSourcemapComment;
- // }
- let worker = new MappedWorker(scriptStr);
-
- return worker as any as Worker;
- }
-}
\ No newline at end of file
diff --git a/web/src/engine/predictive-text/worker-main/src/unwrap.ts b/web/src/engine/predictive-text/worker-main/src/unwrap.ts
deleted file mode 100644
index 6cb84f4da3a..00000000000
--- a/web/src/engine/predictive-text/worker-main/src/unwrap.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * Given a function, this utility returns the source code within it, as a string.
- * This is intended to unwrap the "wrapped" source code created in the LMLayerWorker
- * build process.
- *
- * @param fn The function whose body will be returned.
- */
-export function unwrap(encodedSrc: string): string {
- // There used to be more to this, but now it's a pretty simple passthrough!
- return encodedSrc;
-}
\ No newline at end of file
diff --git a/web/src/engine/predictive-text/worker-main/src/web/index.ts b/web/src/engine/predictive-text/worker-main/src/web/index.ts
index 299f51a0346..36fe8aae5af 100644
--- a/web/src/engine/predictive-text/worker-main/src/web/index.ts
+++ b/web/src/engine/predictive-text/worker-main/src/web/index.ts
@@ -1,7 +1,7 @@
-export { LMLayer } from '../lmlayer.js';
-export { WebWorker } from './web-worker.js';
-export { SourcemappedWorker } from './sourcemappedWorker.js';
+/*
+ * Keyman is copyright (C) SIL Global. MIT License.
+ */
-export interface WorkerFactory {
- constructInstance(): Worker
-}
+export { LMLayer } from '../lmlayer.js';
+export { type WorkerFactory } from '../worker-factory.js';
+export { WebPredictiveTextWorkerFactory } from './web-predictive-text-worker-factory.js';
diff --git a/web/src/engine/predictive-text/worker-main/src/web/sourcemappedWorker.ts b/web/src/engine/predictive-text/worker-main/src/web/sourcemappedWorker.ts
deleted file mode 100644
index 81a7c1d4a35..00000000000
--- a/web/src/engine/predictive-text/worker-main/src/web/sourcemappedWorker.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { unwrap } from '../unwrap.js';
-import { LMLayerWorkerCode, LMLayerWorkerSourcemapComment } from "@keymanapp/lm-worker/worker-main.wrapped.js";
-
-export class SourcemappedWorker {
- // the only difference to WebWorker is that this class uses
- // the unminified LM* blobs
-
- static constructInstance(): Worker {
- return new Worker(this.asBlobURI(LMLayerWorkerCode));
- }
-
- /**
- * Converts the INSIDE of a function into a blob URI that can
- * be passed as a valid URI for a Worker.
- * @param fn Function whose body will be referenced by a URI.
- *
- * This function makes the following possible:
- *
- * let worker = new Worker(LMLayer.asBlobURI(function myWorkerCode () {
- * postMessage('inside Web Worker')
- * function onmessage(event) {
- * // handle message inside Web Worker.
- * }
- * }));
- */
- static asBlobURI(encodedSrc: string): string {
- let code = unwrap(encodedSrc);
-
- // If this is definitively set to either true or false, tree-shaking can take effect.
- // An imported const variable doesn't seem to do it, though.
- // if(false) {
- code += '\n' + LMLayerWorkerSourcemapComment;
- // }
- let blob = new Blob([code], { type: 'text/javascript' });
- return URL.createObjectURL(blob);
- }
-}
\ No newline at end of file
diff --git a/web/src/engine/predictive-text/worker-main/src/web/web-predictive-text-worker-factory.ts b/web/src/engine/predictive-text/worker-main/src/web/web-predictive-text-worker-factory.ts
new file mode 100644
index 00000000000..1b6c5f47244
--- /dev/null
+++ b/web/src/engine/predictive-text/worker-main/src/web/web-predictive-text-worker-factory.ts
@@ -0,0 +1,12 @@
+/*
+ * Keyman is copyright (C) SIL Global. MIT License.
+ */
+
+import { WorkerFactory } from "../worker-factory.js";
+
+export class WebPredictiveTextWorkerFactory implements WorkerFactory {
+ constructor(private workerFilename: string) {}
+ constructInstance(): Worker {
+ return new Worker(this.workerFilename);
+ }
+}
diff --git a/web/src/engine/predictive-text/worker-main/src/web/web-worker.ts b/web/src/engine/predictive-text/worker-main/src/web/web-worker.ts
deleted file mode 100644
index 8e2a8edbbd3..00000000000
--- a/web/src/engine/predictive-text/worker-main/src/web/web-worker.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Keyman is copyright (C) SIL Global. MIT License.
- */
-import { unwrap } from '../unwrap.js';
-import { LMLayerWorkerCode, LMLayerWorkerSourcemapComment } from "@keymanapp/lm-worker/worker-main.wrapped.min.js";
-
-export class WebWorker {
- static constructInstance(): Worker {
- return new Worker(this.asBlobURI(LMLayerWorkerCode));
- }
-
- /**
- * Converts the INSIDE of a function into a blob URI that can
- * be passed as a valid URI for a Worker.
- * @param fn Function whose body will be referenced by a URI.
- *
- * This function makes the following possible:
- *
- * let worker = new Worker(LMLayer.asBlobURI(function myWorkerCode () {
- * postMessage('inside Web Worker')
- * function onmessage(event) {
- * // handle message inside Web Worker.
- * }
- * }));
- */
- static asBlobURI(encodedSrc: string): string {
- let code = unwrap(encodedSrc);
-
- // If this is definitively set to either true or false, tree-shaking can take effect.
- // An imported const variable doesn't seem to do it, though.
- // if(false) {
- code += '\n' + LMLayerWorkerSourcemapComment;
- // }
- const blob = new Blob([code], { type: 'text/javascript' });
- return URL.createObjectURL(blob);
- }
-}
\ No newline at end of file
diff --git a/web/src/engine/predictive-text/worker-main/src/worker-factory.ts b/web/src/engine/predictive-text/worker-main/src/worker-factory.ts
new file mode 100644
index 00000000000..76b79f030d5
--- /dev/null
+++ b/web/src/engine/predictive-text/worker-main/src/worker-factory.ts
@@ -0,0 +1,7 @@
+/*
+ * Keyman is copyright (C) SIL Global. MIT License.
+ */
+
+export interface WorkerFactory {
+ constructInstance(): Worker;
+};
\ No newline at end of file
diff --git a/web/src/engine/predictive-text/worker-main/unit_tests/headless/worker-dummy-integration.tests.js b/web/src/engine/predictive-text/worker-main/unit_tests/headless/worker-dummy-integration.tests.js
index d546edb7040..b2f79caeab1 100644
--- a/web/src/engine/predictive-text/worker-main/unit_tests/headless/worker-dummy-integration.tests.js
+++ b/web/src/engine/predictive-text/worker-main/unit_tests/headless/worker-dummy-integration.tests.js
@@ -4,7 +4,7 @@ import fs from 'fs';
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
-import { LMLayer, SourcemappedWorker as Worker } from '#./node/index.js';
+import { LMLayer, NodePredictiveTextWorkerFactory } from '#./node/index.js';
import { capabilities, iGotDistractedByHazel } from '@keymanapp/common-test-resources/model-helpers.mjs';
/*
@@ -20,7 +20,7 @@ describe('LMLayer using dummy model', function () {
let worker;
beforeEach(function() {
- worker = Worker.constructInstance();
+ worker = (new NodePredictiveTextWorkerFactory()).constructInstance();
lmLayer = new LMLayer(capabilities(), worker);
});
diff --git a/web/src/engine/predictive-text/worker-main/unit_tests/headless/worker-trie-integration.tests.js b/web/src/engine/predictive-text/worker-main/unit_tests/headless/worker-trie-integration.tests.js
index 70583a978ee..e9cf967f205 100644
--- a/web/src/engine/predictive-text/worker-main/unit_tests/headless/worker-trie-integration.tests.js
+++ b/web/src/engine/predictive-text/worker-main/unit_tests/headless/worker-trie-integration.tests.js
@@ -3,7 +3,7 @@ import { assert } from 'chai';
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
-import { LMLayer, SourcemappedWorker as Worker } from '#./node/index.js';
+import { LMLayer, NodePredictiveTextWorkerFactory } from '#./node/index.js';
import { capabilities } from '@keymanapp/common-test-resources/model-helpers.mjs';
/*
@@ -14,7 +14,7 @@ describe('LMLayer using the trie model', function () {
let worker;
beforeEach(function() {
- worker = Worker.constructInstance();
+ worker = (new NodePredictiveTextWorkerFactory()).constructInstance();
lmLayer = new LMLayer(capabilities(), worker, true);
});
diff --git a/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/cases/top-level-lmlayer.tests.ts b/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/cases/top-level-lmlayer.tests.ts
index df5cb45e3bc..ee7de431fa8 100644
--- a/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/cases/top-level-lmlayer.tests.ts
+++ b/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/cases/top-level-lmlayer.tests.ts
@@ -1,47 +1,18 @@
import { assert } from 'chai';
-import { LMLayer, WebWorker } from "@keymanapp/lexical-model-layer/web";
+import { LMLayer, WebPredictiveTextWorkerFactory } from "@keymanapp/lexical-model-layer/web";
import { DEFAULT_BROWSER_TIMEOUT } from '@keymanapp/common-test-resources/test-timeouts.mjs';
-import { defaultCapabilities } from '../helpers.mjs';
+import { defaultCapabilities, predictiveTextWorkerFilename } from '../helpers.mjs';
describe('LMLayer', function () {
this.timeout(DEFAULT_BROWSER_TIMEOUT);
describe('[[constructor]]', function () {
it('should construct with a single argument', function () {
- let lmLayer = new LMLayer(defaultCapabilities, WebWorker.constructInstance(), true);
+ let lmLayer = new LMLayer(defaultCapabilities, (new WebPredictiveTextWorkerFactory(predictiveTextWorkerFilename)).constructInstance(), true);
assert.instanceOf(lmLayer, LMLayer);
lmLayer.shutdown();
});
});
-
- describe('#asBlobURI()', function () {
- // #asBlobURI() requires browser APIs, hence why it cannot be tested headless in Node.
- it('should take a function and convert it into a blob function', function (done) {
- function dummyHandler() {
- // Post something weird, so we can be reasonably certain the Web Worker is...
- // well, working.
- // WARNING: Do NOT refactor this string as a variable. It **MUST** remain a string
- // in this function body, because the code in this function's body gets
- // stringified!
- postMessage('fhqwhgads');
- }
-
- // Note: the full declaration exists; the code we want is wrapped within the func.
- // So... let's just call the func.
- const workerSrc = dummyHandler.toString() + "\ndummyHandler()";
- let uri = WebWorker.asBlobURI(workerSrc);
- assert.match(uri, /^blob:/);
-
- let worker = new Worker(uri);
- worker.onmessage = function thisShouldBeCalled(event) {
- assert.propertyVal(event, 'data', 'fhqwhgads');
- worker.terminate();
- done();
- };
-
- worker.postMessage('test');
- })
- })
});
diff --git a/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/cases/worker-dummy-integration.tests.ts b/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/cases/worker-dummy-integration.tests.ts
index 04af7ec8dcc..b1ed0126a4e 100644
--- a/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/cases/worker-dummy-integration.tests.ts
+++ b/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/cases/worker-dummy-integration.tests.ts
@@ -1,9 +1,9 @@
import { assert } from 'chai';
-import { LMLayer, WebWorker } from "@keymanapp/lexical-model-layer/web";
+import { LMLayer, WebPredictiveTextWorkerFactory } from "@keymanapp/lexical-model-layer/web";
import { DEFAULT_BROWSER_TIMEOUT } from '@keymanapp/common-test-resources/test-timeouts.mjs';
-import { defaultCapabilities } from '../helpers.mjs';
+import { defaultCapabilities, predictiveTextWorkerFilename } from '../helpers.mjs';
// Import assertions, even using 'with', aren't yet supported in Firefox's engine.
// import hazelModel from '@keymanapp/common-test-resources/json/models/future_suggestions/i_got_distracted_by_hazel.json' with { type: 'json' };
@@ -28,7 +28,7 @@ describe('LMLayer using dummy model', function () {
let loc = document.location;
// config.testFile generally starts with a '/', with the path resembling the actual full local
// filesystem for the drive.
- domain = `${loc.protocol}/${loc.host}`
+ domain = `${loc.protocol}//${loc.host}`;
// Test-config setups will take care of the rest; the server-path will be rooted at the repo root.
// With aliasing for resources/.
@@ -52,7 +52,7 @@ describe('LMLayer using dummy model', function () {
describe('Prediction', function () {
it('will predict future suggestions', function () {
- var lmLayer = new LMLayer(defaultCapabilities, WebWorker.constructInstance(), true);
+ var lmLayer = new LMLayer(defaultCapabilities, (new WebPredictiveTextWorkerFactory(predictiveTextWorkerFilename)).constructInstance(), true);
var stripIDs = function(suggestions) {
suggestions.forEach(function(suggestion) {
@@ -93,7 +93,7 @@ describe('LMLayer using dummy model', function () {
describe('Wordbreaking', function () {
it('will perform (default) wordbreaking and return word at caret', function () {
- var lmLayer = new LMLayer(defaultCapabilities, WebWorker.constructInstance());
+ var lmLayer = new LMLayer(defaultCapabilities, (new WebPredictiveTextWorkerFactory(predictiveTextWorkerFilename)).constructInstance());
// We're testing many as asynchronous messages in a row.
// this would be cleaner using async/await syntax, but
diff --git a/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/cases/worker-trie-integration.tests.ts b/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/cases/worker-trie-integration.tests.ts
index bf8d0aef5c8..32367f7ba1e 100644
--- a/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/cases/worker-trie-integration.tests.ts
+++ b/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/cases/worker-trie-integration.tests.ts
@@ -1,8 +1,8 @@
import { assert } from 'chai';
-import { LMLayer, WebWorker } from "@keymanapp/lexical-model-layer/web";
+import { LMLayer, WebPredictiveTextWorkerFactory } from "@keymanapp/lexical-model-layer/web";
import { DEFAULT_BROWSER_TIMEOUT } from '@keymanapp/common-test-resources/test-timeouts.mjs';
-import { defaultCapabilities } from '../helpers.mjs';
+import { defaultCapabilities, predictiveTextWorkerFilename } from '../helpers.mjs';
// Import assertions, even using 'with', aren't yet supported in Firefox's engine.
// import hazelModel from '@keymanapp/common-test-resources/json/models/future_suggestions/i_got_distracted_by_hazel.json' with { type: 'json' };
@@ -17,7 +17,7 @@ describe('LMLayer using the trie model', function () {
before(async () => {
let loc = document.location;
- domain = `${loc.protocol}/${loc.host}`;
+ domain = `${loc.protocol}//${loc.host}`;
// Test-config setups will take care of the rest; the server-path will be rooted at the repo root.
// With aliasing for resources/.
@@ -29,7 +29,7 @@ describe('LMLayer using the trie model', function () {
// Parameter 3 = true: enables 'test mode', disables correction-search timeout.
// This helps prevent the correction-search timeout from flaking out periodically during unit tests in
// CI, since remote servers / devices are involved.
- var lmLayer = new LMLayer(defaultCapabilities, WebWorker.constructInstance(), true);
+ var lmLayer = new LMLayer(defaultCapabilities, (new WebPredictiveTextWorkerFactory(predictiveTextWorkerFilename)).constructInstance(), true);
// We're testing many as asynchronous messages in a row.
// this would be cleaner using async/await syntax, but
@@ -71,7 +71,7 @@ describe('LMLayer using the trie model', function () {
//
// https://community.software.sil.org/t/search-term-to-key-in-lexical-model-not-working-both-ways-by-default/3133
it('should use the default searchTermToKey()', function () {
- var lmLayer = new LMLayer(defaultCapabilities, WebWorker.constructInstance(), /* testMode */ true);
+ var lmLayer = new LMLayer(defaultCapabilities, (new WebPredictiveTextWorkerFactory(predictiveTextWorkerFilename)).constructInstance(), /* testMode */ true);
let loc = document.location;
return lmLayer.loadModel(
diff --git a/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/helpers.mjs b/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/helpers.mjs
index 385379bde64..fc4a0fc2ce7 100644
--- a/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/helpers.mjs
+++ b/web/src/engine/predictive-text/worker-main/unit_tests/in_browser/helpers.mjs
@@ -1,3 +1,9 @@
+/*
+ * Keyman is copyright (C) SIL Global. MIT License.
+ */
+
export let defaultCapabilities = {
maxLeftContextCodeUnits: 64
-};
\ No newline at end of file
+};
+
+export const predictiveTextWorkerFilename = 'web/build/publish/debug/worker-thread.js';
\ No newline at end of file
diff --git a/web/src/engine/predictive-text/worker-thread/build-polyfiller.js b/web/src/engine/predictive-text/worker-thread/build-polyfiller.js
deleted file mode 100644
index eab268e9867..00000000000
--- a/web/src/engine/predictive-text/worker-thread/build-polyfiller.js
+++ /dev/null
@@ -1,196 +0,0 @@
-import fs from 'fs';
-
-import SourceMapCombiner from 'combine-source-map';
-import convertSourceMap from 'convert-source-map'; // Transforms sourcemaps among various common formats.
- // Base64, stringified-JSON, end-of-file comment...
-
-import esbuild from 'esbuild';
-
-let sourceFromArgs;
-let destFromArgs;
-
-function doHelp(errMessage) {
- if(errMessage) {
- console.error(errMessage + '\n');
- }
- console.log(`
-Summary:
- Creates a polyfilled version of the lm-worker to ensure compatibility with the needs of
- Keyman for Android when run on Android 5.0 / API 21 without an updated Chrome webview.
-
-Usage:
- node build-polyfiller.js [options...]
-
-Parameters:
- : Fully-bundled and compiled JS file to be polyfilled.
-
-Options:
- --help Shows this script's documentation
- --out Specifies the destination path for the polyfilled output.
-
- If missing, the output will be placed next to the source and given
- the same path, but with '.polyfilled.js' replacing the original '.js'
- extension.
-
- Either way, a minified version will also be output, using the same
- path but with the final '.js' replaced by '.min.js'.
-` );
- process.exit(errMessage ? 1 : 0);
-}
-
-if(process.argv.length > 2) {
- for(let i = 2; i < process.argv.length; i++) {
- const arg = process.argv[i];
-
- switch(arg) {
- case '--help':
- doHelp();
- break;
- case '--out':
- destFromArgs = process.argv[++i];
- break;
- default:
- if(!sourceFromArgs) {
- sourceFromArgs = arg;
- } else {
- doHelp("Input file can only be specified once; aborting");
- }
- }
- }
-} else {
- // Display help + abort.
- doHelp("Required parameters missing");
-}
-
-if(!sourceFromArgs || sourceFromArgs.substring(sourceFromArgs.length - 3) != '.js') {
- doHelp("No input file has been specified; aborting.");
-}
-
-const sourceFile = sourceFromArgs;
-const destFile = destFromArgs || sourceFromArgs.substring(0, sourceFromArgs.length - 3) + '.polyfilled.js';
-const minDestFile = destFile.substring(0, destFile.length - 3) + '.min.js';
-
-let loadPolyfill = function(scriptFile, sourceMapFile) {
- // May want to retool the pathing somewhat!
- return {
- source: fs.readFileSync(scriptFile).toString(),
- sourceFile: sourceMapFile || scriptFile
- };
-}
-
-let loadCompiledModuleFilePair = function(file, mapFilename) {
- let module = fs.readFileSync(file);
- let moduleSourcemapJSON = JSON.parse(fs.readFileSync(file + '.map').toString());
-
- // May want to retool the pathing somewhat!
- return {
- plainSource: `${module}`,
- sourceMapAsJSON: moduleSourcemapJSON,
- sourceFile: mapFilename || file,
-
- /**The source + inlined sourcemap-as-comment used by `combine-source-map`. */
- get source() {
- let jsonAsBuffer = Buffer.from(JSON.stringify(this.sourceMapAsJSON));
- return `${this.plainSource}\n${convertSourceMap.fromJSON(jsonAsBuffer).toComment()}`;
- }
- };
-}
-
-function concatScriptsAndSourcemaps(files, finalName, separatorFile) {
- let combiner = SourceMapCombiner.create(finalName);
-
- let finalConcatenationArray = [];
- let lineCountThusFar = 0;
-
- for(let filePairing of files) {
- let offset = {
- line: lineCountThusFar
- };
-
- console.log(`- ${filePairing.sourceFile}`);
- combiner = combiner.addFile(filePairing, offset);
-
- let rawSourceToConcat = filePairing.plainSource || filePairing.source;
- lineCountThusFar += rawSourceToConcat.split('\n').length + 1; // Not sure why it needs the fudge-factor, but it does.
-
- finalConcatenationArray.push(filePairing);
- if(filePairing != files[files.length-1]) {
- combiner = combiner.addFile(separatorFile);
- finalConcatenationArray.push(separatorFile);
- }
- }
-
- let bundledSource = finalConcatenationArray.map((pair => pair.plainSource || pair.source)).join('');
-
- return {
- script: bundledSource,
- sourcemapJSON: JSON.parse(convertSourceMap.fromBase64(combiner.base64()).toJSON()),
- scriptFilename: finalName
- }
-}
-
-// Centralized?
-
-console.log("Pass 1: worker + polyfill concatenation");
-
-let separatorFile = {
- source: `
-
- `,
- sourceFile: ''
-}
-
-let sourceFileSet = [
- // Needed for Android / Chromium browser pre-41.
- loadPolyfill('../../../node_modules/string.prototype.codepointat/codepointat.js', 'src/polyfills/string.codepointat.js'),
- // Needed for Android / Chromium browser pre-45.
- // Not used in this codebase, but used by some compiled model defaults.
- loadPolyfill('src/polyfills/array.from.js', 'src/polyfills/array.from.js'),
- // Needed for Android / Chromium browser pre-45.
- loadPolyfill('src/polyfills/array.fill.js', 'src/polyfills/array.fill.js'),
- // Needed for Android / Chromium browser pre-45.
- loadPolyfill('src/polyfills/array.findIndex.js', 'src/polyfills/array.findIndex.js'),
- // Needed for Android / Chromium browser pre-47.
- loadPolyfill('src/polyfills/array.includes.js', 'src/polyfills/array.includes.js'),
- // For Object.values, for iteration over object-based associate arrays.
- // Needed for Android / Chromium browser pre-54.
- loadPolyfill('src/polyfills/object.values.js', 'src/polyfills/object.values.js'),
- // Needed to support Symbol.iterator, as used by the correction algorithm.
- // Needed for Android / Chromium browser pre-43.
- loadPolyfill('src/polyfills/symbol-es6.min.js', 'src/polyfills/symbol-es6.min.js'),
- loadCompiledModuleFilePair(sourceFile, sourceFile)
-];
-
-let fullWorkerConcatenation = concatScriptsAndSourcemaps(sourceFileSet, destFile, separatorFile);
-
-// New stage: cleaning the sourcemaps
-
-// Sources are being passed into the sourcemap concatenator via our working directory.
-let sourceRoot = '@keymanapp/keyman/web/src/engine/predictive-text/worker-thread/';
-fullWorkerConcatenation.sourcemapJSON.sourceRoot = sourceRoot;
-
-// End "cleaning the sourcemaps"
-
-console.log();
-console.log("Pass 2: Output intermediate state and perform minification");
-
-// IMPORTANT: Remove file-end sourcemap ref comment and replace it!
-fullWorkerConcatenation.script = fullWorkerConcatenation.script.substring(0, fullWorkerConcatenation.script.lastIndexOf('//# sourceMappingURL'));
-fullWorkerConcatenation.script += `//# sourceMappingURL=${fullWorkerConcatenation.scriptFilename}.map`;
-
-fs.writeFileSync(fullWorkerConcatenation.scriptFilename, fullWorkerConcatenation.script);
-if(fullWorkerConcatenation.sourcemapJSON) {
- fs.writeFileSync(`${fullWorkerConcatenation.scriptFilename}.map`, convertSourceMap.fromObject(fullWorkerConcatenation.sourcemapJSON).toJSON());
-}
-
-await esbuild.build({
- entryPoints: [destFile],
- sourcemap: 'external',
- sourcesContent: true,
- minify: true,
- // Do NOT enable - will break under Android 5.0 / Chrome 35 environments, likely through Chrome 42.
- // https://caniuse.com/mdn-javascript_builtins_function_name_configurable_true
- keepNames: false,
- target: 'es6',
- outfile: minDestFile
-});
diff --git a/web/src/engine/predictive-text/worker-thread/build-wrapper.js b/web/src/engine/predictive-text/worker-thread/build-wrapper.js
deleted file mode 100644
index 66fff07b6f3..00000000000
--- a/web/src/engine/predictive-text/worker-thread/build-wrapper.js
+++ /dev/null
@@ -1,121 +0,0 @@
-import fs from 'fs';
-
-import convertSourcemap from 'convert-source-map'; // Transforms sourcemaps among various common formats.
- // Base64, stringified-JSON, end-of-file comment...
-
-let INCLUDE_SRCMAPS = false;
-
-let sourceFromArgs;
-let destFromArgs;
-
-function doHelp(errMessage) {
- if(errMessage) {
- console.error(errMessage + '\n');
- }
-
- console.log(`
-Summary:
- Creates a "wrapped" version of the lm-worker for compilation into and inclusion as part
- of a different JS bundle for later use as the core of a predictive-text WebWorker.
-
-Usage:
- node build-wrapper.js [options...]
-
-Parameters:
- : Fully-bundled and compiled JS file to be wrapped.
-
-Options:
- --help Shows this script's documentation
- --out Specifies the destination path for the wrapped output.
-
- If missing, the output will be placed next to the source and given
- the same path, but with '.wrapped.js' replacing the original '.js'
- extension.
- --sourceMap Includes the script's original sourcemaps within the wrapped output
-` );
- process.exit(errMessage ? 1 : 0);
-}
-
-if(process.argv.length > 2) {
- for(let i = 2; i < process.argv.length; i++) {
- const arg = process.argv[i];
-
- switch(arg) {
- case '--help':
- doHelp();
- break;
- case '--sourceMap': // bc TS uses this exact flag. esbuild... uses sourcemap (in the JS config)
- case '--sourcemap':
- case '--source-map':
- INCLUDE_SRCMAPS = true;
- break;
- case '--out':
- destFromArgs = process.argv[++i];
- break;
- default:
- if(!sourceFromArgs) {
- sourceFromArgs = arg;
- } else {
- doHelp("Input file can only be specified once; aborting");
- }
- }
- }
-} else {
- // Display help + abort.
- doHelp("Required parameters missing");
-}
-
-if(!sourceFromArgs || sourceFromArgs.substring(sourceFromArgs.length - 3) != '.js') {
- doHelp("No input file has been specified; aborting.");
-}
-
-const sourceFile = sourceFromArgs;
-const destFile = destFromArgs || sourceFromArgs.substring(0, sourceFromArgs.length - 3) + '.wrapped.js';
-
-// Now, to build the wrapper...
-
-const script = fs.readFileSync(sourceFile);
-
-// Wrapped in a function so we can leverage `const` with the result.
-function buildSrcMapString() {
- const sourcemapJSON = convertSourcemap.fromJSON(fs.readFileSync(`${sourceFile}.map`)).toObject();
- const encodedSrcMap = convertSourcemap.fromObject(sourcemapJSON).toBase64();
- return `//# sourceMappingURL=data:application/json;charset=utf-8;base64,${encodedSrcMap}`;
-}
-
-// While it IS possible to do partial sourcemaps (without the sources, but with everything else) within the worker...
-// the resulting sourcemaps are -surprisingly- large - larger than the code itself!
-const srcMapString = INCLUDE_SRCMAPS ? buildSrcMapString() : "";
-
-/*
- * It'd be nice to do a 'partial' encodeURIComponent that only gets the important bits...
- * but my attempts to do so end up triggering errors when loading.
- */
-
-let rawScript = script.toString();
-// Two layers of encoding: one for the raw source (parsed by the JS engine),
-// one to 'unwrap' it from a string _within_ that source.
-let jsonEncoded = JSON.stringify(rawScript);
-
-let wrapper = `
-// Autogenerated code. Do not modify!
-// --START:LMLayerWorkerCode--
-
-export var LMLayerWorkerCode = ${jsonEncoded};
-
-${!INCLUDE_SRCMAPS && "// Sourcemaps have been omitted for this release build." || ''}
-export var LMLayerWorkerSourcemapComment = "${srcMapString}";
-
-// --END:LMLayerWorkerCode
-`;
-
-fs.writeFileSync(destFile, wrapper);
-
-// Don't let there be multiples; TS may signal typing conflict if it thinks
-// these are defined multiple times.
-let wrapperDeclarations = `
-export const LMLayerWorkerCode: string;
-export const LMLayerWorkerSourcemapComment: string;
-`;
-
-fs.writeFileSync('build/lib/worker-main.wrapped.d.ts', wrapperDeclarations);
\ No newline at end of file
diff --git a/web/src/engine/predictive-text/worker-thread/build.sh b/web/src/engine/predictive-text/worker-thread/build.sh
index 2e7fd9d90e5..31c7ac4b586 100755
--- a/web/src/engine/predictive-text/worker-thread/build.sh
+++ b/web/src/engine/predictive-text/worker-thread/build.sh
@@ -34,7 +34,7 @@ builder_describe \
builder_describe_outputs \
configure /node_modules \
- build "/web/src/engine/predictive-text/worker-thread/${LIB}/worker-main.wrapped.min.js"
+ build "/web/src/engine/predictive-text/worker-thread/${LIB}/worker-thread.min.js"
builder_parse "$@"
@@ -48,7 +48,7 @@ function do_configure() {
function do_build() {
# Declaration bundling.
- tsc --emitDeclarationOnly --outFile $INTERMEDIATE/worker-main.d.ts
+ tsc --emitDeclarationOnly --outFile $INTERMEDIATE/worker-thread.d.ts
# Some automated tests currently rely upon the individual output files.
tsc
@@ -57,19 +57,19 @@ function do_build() {
# The ES6 target needs no polyfills - we go straight to the wrapped version.
- node_es_bundle src/main/worker-main.ts \
- --out $INTERMEDIATE/worker-main.js \
+ node_es_bundle src/main/worker-thread.ts \
+ --out $INTERMEDIATE/worker-thread.js \
--charset "utf8" \
--target "es6" \
--sourceRoot '@keymanapp/keyman/web/src/engine/predictive-text/worker-thread/src/main'
node "$SRCMAP_CLEANER" \
- $INTERMEDIATE/worker-main.js.map \
- $INTERMEDIATE/worker-main.js.map \
+ $INTERMEDIATE/worker-thread.js.map \
+ $INTERMEDIATE/worker-thread.js.map \
--clean
- node_es_bundle src/main/worker-main.ts \
- --out $INTERMEDIATE/worker-main.min.js \
+ node_es_bundle src/main/worker-thread.ts \
+ --out $INTERMEDIATE/worker-thread.min.js \
--minify \
--charset "utf8" \
--profile build/filesize-profile.log \
@@ -77,15 +77,16 @@ function do_build() {
--sourceRoot '@keymanapp/keyman/web/src/engine/predictive-text/worker-thread/src/main'
node "$SRCMAP_CLEANER" \
- $INTERMEDIATE/worker-main.min.js.map \
- $INTERMEDIATE/worker-main.min.js.map \
+ $INTERMEDIATE/worker-thread.min.js.map \
+ $INTERMEDIATE/worker-thread.min.js.map \
--clean
- node build-wrapper.js $INTERMEDIATE/worker-main.js \
- --out $LIB/worker-main.wrapped.js \
- --sourceMap
- node build-wrapper.js $INTERMEDIATE/worker-main.min.js \
- --out $LIB/worker-main.wrapped.min.js
+ cp $INTERMEDIATE/worker-thread.js $LIB/worker-thread.js
+ cp $INTERMEDIATE/worker-thread.js.map $LIB/worker-thread.js.map
+ cp $INTERMEDIATE/worker-thread.d.ts $LIB/worker-thread.d.ts
+ cp $INTERMEDIATE/worker-thread.min.js $LIB/worker-thread.min.js
+ cp $INTERMEDIATE/worker-thread.min.js.map $LIB/worker-thread.min.js.map
+ # cp $INTERMEDIATE/worker-thread.min.d.ts $LIB/worker-thread.min.d.ts
}
function do_test() {
diff --git a/web/src/engine/predictive-text/worker-thread/package.json b/web/src/engine/predictive-text/worker-thread/package.json
index af87bce8114..b5b855664d3 100644
--- a/web/src/engine/predictive-text/worker-thread/package.json
+++ b/web/src/engine/predictive-text/worker-thread/package.json
@@ -1,15 +1,15 @@
{
"name": "@keymanapp/lm-worker",
"description": "Keyman/Predictive Text worker",
- "main": "./build/lib/worker-main.wrapped.js",
+ "main": "./build/lib/worker-thread.js",
"exports": {
- "./worker-main.wrapped.js": {
- "types": "./build/lib/worker-main.wrapped.d.ts",
- "default": "./build/lib/worker-main.wrapped.js"
+ "./worker-thread.js": {
+ "types": "./build/lib/worker-thread.d.ts",
+ "default": "./build/lib/worker-thread.js"
},
- "./worker-main.wrapped.min.js": {
- "types": "./build/lib/worker-main.wrapped.d.ts",
- "default": "./build/lib/worker-main.wrapped.min.js"
+ "./worker-thread.min.js": {
+ "types": "./build/lib/worker-thread.d.ts",
+ "default": "./build/lib/worker-thread.min.js"
},
"./test-index": {
"default": "./build/obj/test-index.js"
diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/execution-timer.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/execution-timer.ts
index 1ba6f535a00..21d42b2f961 100644
--- a/web/src/engine/predictive-text/worker-thread/src/main/correction/execution-timer.ts
+++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/execution-timer.ts
@@ -369,7 +369,7 @@ export class ExecutionTimer {
// By using the state field, we'll get an error if we somehow call other
// class methods during the defer.
this.activeSpan = new ExecutionSpan(this.deferBucket, () => this.activeSpan = null);
- // WebWorker messages appear to come in via the macrotask queue.
+ // Worker messages appear to come in via the macrotask queue.
await timedPromise(minWait);
this.activeSpan.end();
diff --git a/web/src/engine/predictive-text/worker-thread/src/main/worker-main.ts b/web/src/engine/predictive-text/worker-thread/src/main/worker-thread.ts
similarity index 100%
rename from web/src/engine/predictive-text/worker-thread/src/main/worker-main.ts
rename to web/src/engine/predictive-text/worker-thread/src/main/worker-thread.ts
diff --git a/web/src/engine/predictive-text/worker-thread/src/tests/test-runner/cases/worker.tests.ts b/web/src/engine/predictive-text/worker-thread/src/tests/test-runner/cases/worker.tests.ts
index fcae30e7623..45a95c8a292 100644
--- a/web/src/engine/predictive-text/worker-thread/src/tests/test-runner/cases/worker.tests.ts
+++ b/web/src/engine/predictive-text/worker-thread/src/tests/test-runner/cases/worker.tests.ts
@@ -1,22 +1,12 @@
-import { assert } from 'chai';
-import { LMLayerWorkerCode } from "@keymanapp/lm-worker/worker-main.wrapped.js";
import { DEFAULT_BROWSER_TIMEOUT } from '@keymanapp/common-test-resources/test-timeouts.mjs';
describe('LMLayerWorker', function () {
- // This one makes multiple subsequent calls across the WebWorker boundary, so we should be generous here.
+ // This one makes multiple subsequent calls across the PredictiveTextWorker boundary, so we should be generous here.
this.timeout(DEFAULT_BROWSER_TIMEOUT);
- describe('LMLayerWorkerCode', function() {
- it('should exist!', function() {
- assert.isString(LMLayerWorkerCode);
- });
- });
-
describe('Usage within a Web Worker', function () {
it('should install itself in the worker context', function (done) {
- let blob = new Blob([LMLayerWorkerCode], { type: 'text/javascript' });
- let uri = URL.createObjectURL(blob);
- let worker = new Worker(uri);
+ let worker = new Worker('/engine/predictive-text/worker-thread/build/lib/worker-thread.js');
worker.onmessage = function thisShouldBeCalled(message) {
done();
worker.terminate();
@@ -30,10 +20,9 @@ describe('LMLayerWorker', function () {
});
worker.postMessage({
message: 'load',
- // Since the worker's based in a blob, it's not on the 'same domain'. We need to absolute-path the model file.
source: {
type: 'file',
- file: document.location.protocol + '//' + document.location.host + "/resources/models/simple-dummy.js"
+ file: "./resources/models/simple-dummy.js"
}
});
});
diff --git a/web/src/engine/src/main/headless/inputProcessor.ts b/web/src/engine/src/main/headless/inputProcessor.ts
index b2f4531a5b0..103ae9ac1bc 100644
--- a/web/src/engine/src/main/headless/inputProcessor.ts
+++ b/web/src/engine/src/main/headless/inputProcessor.ts
@@ -27,7 +27,7 @@ import {
import { TranscriptionCache } from "./transcriptionCache.js";
import { LexicalModelTypes } from '@keymanapp/common-types';
-import { WorkerFactory } from "@keymanapp/lexical-model-layer";
+import { type WorkerFactory } from "@keymanapp/lexical-model-layer";
// Only consider raw-insertion transforms. Delete-left and delete-right disqualify an
// incoming transform from reverting post-suggestion whitespace (or similar).
diff --git a/web/src/engine/src/main/headless/languageProcessor.ts b/web/src/engine/src/main/headless/languageProcessor.ts
index 20515c22885..0107d364f99 100644
--- a/web/src/engine/src/main/headless/languageProcessor.ts
+++ b/web/src/engine/src/main/headless/languageProcessor.ts
@@ -1,5 +1,5 @@
import { EventEmitter } from "eventemitter3";
-import { LMLayer, WorkerFactory } from "@keymanapp/lexical-model-layer/web";
+import { LMLayer, type WorkerFactory } from "@keymanapp/lexical-model-layer/web";
import { Transcription, TextStoreLanguageProcessorInterface, SyntheticTextStore, ProcessorAction } from 'keyman/engine/keyboard';
import { LanguageProcessorEventMap, ModelSpec, StateChangeEnum, ReadySuggestions } from 'keyman/engine/interfaces';
import { ContextWindow } from "./contextWindow.js";
diff --git a/web/src/engine/src/main/keymanEngineBase.ts b/web/src/engine/src/main/keymanEngineBase.ts
index 2ee22a2b370..1318818c288 100644
--- a/web/src/engine/src/main/keymanEngineBase.ts
+++ b/web/src/engine/src/main/keymanEngineBase.ts
@@ -4,7 +4,7 @@
import { type KeyEvent, JSKeyboard, Keyboard, KeyboardProperties, KeyboardKeymanGlobal, ProcessorAction } from "keyman/engine/keyboard";
import { ProcessorInitOptions } from 'keyman/engine/js-processor';
import { DOMKeyboardLoader } from "keyman/engine/keyboard";
-import { WorkerFactory } from "@keymanapp/lexical-model-layer/web"
+import { type WorkerFactory } from "@keymanapp/lexical-model-layer/web"
import { InputProcessor } from './headless/inputProcessor.js';
import { OSKView } from "keyman/engine/osk";
import { KeyboardRequisitioner, ModelCache, toUnprefixedKeyboardId, DOMCloudRequester } from "keyman/engine/keyboard-storage";
@@ -107,10 +107,7 @@ export class KeymanEngineBase<
};
/**
- * @param worker A configured WebWorker to serve as the predictive-text engine's main thread.
- * Available in the following variants:
- * - sourcemapped, unminified (debug)
- * - non-sourcemapped + minified (release)
+ * @param worker A factory that provides a Worker that serves as the predictive-text engine's main thread.
* @param config
* @param contextManager
* @param processorConfigInitializer A one-time use closure used to initialize certain critical components reliant
diff --git a/web/src/test/auto/dom/cases/gesture-processor/canary.tests.ts b/web/src/test/auto/dom/cases/gesture-processor/canary.tests.ts
index 7c3fde9b85a..fe030396d04 100644
--- a/web/src/test/auto/dom/cases/gesture-processor/canary.tests.ts
+++ b/web/src/test/auto/dom/cases/gesture-processor/canary.tests.ts
@@ -22,7 +22,7 @@ describe("'Canary' checks", function() {
let loc = document.location;
// config.testFile generally starts with a '/', with the path resembling the actual full local
// filesystem for the drive.
- domain = `${loc.protocol}/${loc.host}`
+ domain = `${loc.protocol}//${loc.host}`;
// Test-config setups will take care of the rest; the server-path will be rooted at the repo root.
// With aliasing for resources/.
diff --git a/web/src/test/auto/dom/cases/gesture-processor/recordedCoordSequences.tests.ts b/web/src/test/auto/dom/cases/gesture-processor/recordedCoordSequences.tests.ts
index 61da5cd09c7..7a7a9a69c6f 100644
--- a/web/src/test/auto/dom/cases/gesture-processor/recordedCoordSequences.tests.ts
+++ b/web/src/test/auto/dom/cases/gesture-processor/recordedCoordSequences.tests.ts
@@ -17,7 +17,7 @@ function isOnAndroid() {
const loc = document.location;
// config.testFile generally starts with a '/', with the path resembling the actual full local
// filesystem for the drive.
-const domain = `${loc.protocol}/${loc.host}`
+const domain = `${loc.protocol}//${loc.host}`;
async function fetchRecording(jsonFilename: string): Promise {
const jsonResponse = await fetch(new URL(`${domain}/resources/json/${jsonFilename}.json`));
diff --git a/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.tests.ts b/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.tests.ts
index 15faf2189fd..bd8ab1116d7 100644
--- a/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.tests.ts
+++ b/web/src/test/auto/headless/engine/interfaces/prediction/predictionContext.tests.ts
@@ -5,10 +5,11 @@ import { LexicalModelTypes } from '@keymanapp/common-types';
import { LanguageProcessor, TranscriptionCache } from 'keyman/engine/main';
import { PredictionContext } from 'keyman/engine/interfaces';
-import { NodeWorker } from "@keymanapp/lexical-model-layer/node";
+import { NodePredictiveTextWorkerFactory } from "@keymanapp/lexical-model-layer/node";
import { SyntheticTextStore } from 'keyman/engine/keyboard';
import Suggestion = LexicalModelTypes.Suggestion;
+import { getPredictiveTextWorkerPath } from 'keyman/test/resources';
function compileDummyModel(suggestionSets: Suggestion[][]) {
return `
@@ -70,7 +71,7 @@ describe("PredictionContext", () => {
let langProcessor: LanguageProcessor;
beforeEach(function() {
- langProcessor = new LanguageProcessor(NodeWorker, new TranscriptionCache());
+ langProcessor = new LanguageProcessor(new NodePredictiveTextWorkerFactory(getPredictiveTextWorkerPath()), new TranscriptionCache());
});
afterEach(function() {
diff --git a/web/src/test/auto/headless/engine/main/inputProcessor.tests.ts b/web/src/test/auto/headless/engine/main/inputProcessor.tests.ts
index d76b774d61a..9ae40b8b200 100644
--- a/web/src/test/auto/headless/engine/main/inputProcessor.tests.ts
+++ b/web/src/test/auto/headless/engine/main/inputProcessor.tests.ts
@@ -7,7 +7,7 @@ import * as sinon from 'sinon';
import { LexicalModelTypes } from '@keymanapp/common-types';
import { KeyboardTest, RecordedPhysicalKeystroke, RecordedSequenceTestSet } from '@keymanapp/recorder-core';
-import { NodeWorker } from '@keymanapp/lexical-model-layer/node';
+import { NodePredictiveTextWorkerFactory } from '@keymanapp/lexical-model-layer/node';
import { DeviceSpec, KMWString } from 'keyman/common/web-utils';
import { InputProcessor } from 'keyman/engine/main';
@@ -68,7 +68,7 @@ describe('InputProcessor', function() {
// Can construct without the second parameter; if so, the final assertion - .mayPredict
// will be invalidated. (No worker, no ability to predict.)
// @ts-ignore
- core = new InputProcessor(device, NodeWorker, {
+ core = new InputProcessor(device, new NodePredictiveTextWorkerFactory(), {
baseLayout: 'us',
keyboardInterface: new JSKeyboardInterface({}, null, new VariableStoreTestSerializer()),
defaultOutputRules: new DefaultOutputRules()
@@ -103,7 +103,7 @@ describe('InputProcessor', function() {
let testDistribution: KeyDistribution = [];
let keyboardWithHarness: JSKeyboardInterface;
- let mainWebScriptURL = require.resolve('@keymanapp/lm-worker/worker-main.wrapped.js');
+ let mainWebScriptURL = require.resolve('@keymanapp/lm-worker/worker-thread.js');
// Easy peasy long context: use the unminified main script for the predictive-text worker!
let coreSourceCode = fs.readFileSync(mainWebScriptURL, 'utf-8');
@@ -309,7 +309,7 @@ describe('InputProcessor', function() {
});
it('replaces appended whitespace when a manually-applied suggestion is followed by a K_SPACE (dummy models)', async () => {
- const core = new InputProcessor(device, NodeWorker, {...DEFAULT_PROCESSOR_INIT_OPTIONS, keyboardInterface: keyboardWithHarness});
+ const core = new InputProcessor(device, new NodePredictiveTextWorkerFactory(), {...DEFAULT_PROCESSOR_INIT_OPTIONS, keyboardInterface: keyboardWithHarness});
const langProcessor = core.languageProcessor;
try {
@@ -366,7 +366,7 @@ describe('InputProcessor', function() {
});
it('replaces appended whitespace when a manually-applied suggestion is followed by a K_SPACE (trie models)', async () => {
- const core = new InputProcessor(device, NodeWorker, {...DEFAULT_PROCESSOR_INIT_OPTIONS, keyboardInterface: keyboardWithHarness});
+ const core = new InputProcessor(device, new NodePredictiveTextWorkerFactory(), {...DEFAULT_PROCESSOR_INIT_OPTIONS, keyboardInterface: keyboardWithHarness});
const langProcessor = core.languageProcessor;
try {
@@ -424,7 +424,7 @@ describe('InputProcessor', function() {
});
it('auto-applies a suggestion properly when available and triggered appropriately', async () => {
- const core = new InputProcessor(device, NodeWorker, {...DEFAULT_PROCESSOR_INIT_OPTIONS, keyboardInterface: keyboardWithHarness});
+ const core = new InputProcessor(device, new NodePredictiveTextWorkerFactory(), {...DEFAULT_PROCESSOR_INIT_OPTIONS, keyboardInterface: keyboardWithHarness});
const langProcessor = core.languageProcessor;
try {
@@ -476,7 +476,7 @@ describe('InputProcessor', function() {
});
it('displays a reversion after manually applying a suggestion and immediately backspacing', async () => {
- const core = new InputProcessor(device, NodeWorker, {...DEFAULT_PROCESSOR_INIT_OPTIONS, keyboardInterface: keyboardWithHarness});
+ const core = new InputProcessor(device, new NodePredictiveTextWorkerFactory(), {...DEFAULT_PROCESSOR_INIT_OPTIONS, keyboardInterface: keyboardWithHarness});
const langProcessor = core.languageProcessor;
try {
@@ -533,7 +533,7 @@ describe('InputProcessor', function() {
});
it('displays a reversion after returning to the whitespace after a manually-applied suggestion', async () => {
- const core = new InputProcessor(device, NodeWorker, {...DEFAULT_PROCESSOR_INIT_OPTIONS, keyboardInterface: keyboardWithHarness});
+ const core = new InputProcessor(device, new NodePredictiveTextWorkerFactory(), {...DEFAULT_PROCESSOR_INIT_OPTIONS, keyboardInterface: keyboardWithHarness});
const langProcessor = core.languageProcessor;
try {
@@ -594,7 +594,7 @@ describe('InputProcessor', function() {
});
it("displays a reversion after returning to the end of a manually-applied suggestion's body", async () => {
- const core = new InputProcessor(device, NodeWorker, {...DEFAULT_PROCESSOR_INIT_OPTIONS, keyboardInterface: keyboardWithHarness});
+ const core = new InputProcessor(device, new NodePredictiveTextWorkerFactory(), {...DEFAULT_PROCESSOR_INIT_OPTIONS, keyboardInterface: keyboardWithHarness});
const langProcessor = core.languageProcessor;
try {
@@ -656,7 +656,7 @@ describe('InputProcessor', function() {
});
it("does not display a reversion after backspacing part of an applied suggestion", async () => {
- const core = new InputProcessor(device, NodeWorker, {...DEFAULT_PROCESSOR_INIT_OPTIONS, keyboardInterface: keyboardWithHarness});
+ const core = new InputProcessor(device, new NodePredictiveTextWorkerFactory(), {...DEFAULT_PROCESSOR_INIT_OPTIONS, keyboardInterface: keyboardWithHarness});
const langProcessor = core.languageProcessor;
try {
@@ -720,7 +720,7 @@ describe('InputProcessor', function() {
});
it('displays a reversion after auto-applying a suggestion and immediately backspacing', async () => {
- const core = new InputProcessor(device, NodeWorker, {...DEFAULT_PROCESSOR_INIT_OPTIONS, keyboardInterface: keyboardWithHarness});
+ const core = new InputProcessor(device, new NodePredictiveTextWorkerFactory(), {...DEFAULT_PROCESSOR_INIT_OPTIONS, keyboardInterface: keyboardWithHarness});
const langProcessor = core.languageProcessor;
try {
@@ -775,7 +775,7 @@ describe('InputProcessor', function() {
});
it('displays a reversion after returning to the whitespace after a auto-applied suggestion', async () => {
- const core = new InputProcessor(device, NodeWorker, {...DEFAULT_PROCESSOR_INIT_OPTIONS, keyboardInterface: keyboardWithHarness});
+ const core = new InputProcessor(device, new NodePredictiveTextWorkerFactory(), {...DEFAULT_PROCESSOR_INIT_OPTIONS, keyboardInterface: keyboardWithHarness});
const langProcessor = core.languageProcessor;
try {
@@ -829,7 +829,7 @@ describe('InputProcessor', function() {
});
it("displays a reversion after returning to the end of an auto-applied suggestion's body", async () => {
- const core = new InputProcessor(device, NodeWorker, {...DEFAULT_PROCESSOR_INIT_OPTIONS, keyboardInterface: keyboardWithHarness});
+ const core = new InputProcessor(device, new NodePredictiveTextWorkerFactory(), {...DEFAULT_PROCESSOR_INIT_OPTIONS, keyboardInterface: keyboardWithHarness});
const langProcessor = core.languageProcessor;
try {
diff --git a/web/src/test/auto/headless/engine/main/languageProcessor.tests.ts b/web/src/test/auto/headless/engine/main/languageProcessor.tests.ts
index 126feab306f..b23f557b572 100644
--- a/web/src/test/auto/headless/engine/main/languageProcessor.tests.ts
+++ b/web/src/test/auto/headless/engine/main/languageProcessor.tests.ts
@@ -8,7 +8,7 @@ import path from 'node:path';
import { assert } from 'chai';
-import { SourcemappedWorker as LMWorker } from "@keymanapp/lexical-model-layer/node";
+import { NodePredictiveTextWorkerFactory } from "@keymanapp/lexical-model-layer/node";
import { LexicalModelCompiler } from '@keymanapp/kmc-model';
import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers';
import { SyntheticTextStore } from 'keyman/engine/keyboard';
@@ -16,6 +16,7 @@ import { LanguageProcessor, TranscriptionCache } from 'keyman/engine/main';
import { ModelSpec } from 'keyman/engine/interfaces';
import { MinimalKeymanGlobal } from 'keyman/engine/keyboard';
+import { getPredictiveTextWorkerPath } from 'keyman/test/resources';
const KEYMAN_ROOT = env.KEYMAN_ROOT;
@@ -35,7 +36,7 @@ describe('LanguageProcessor', function() {
const callbacks = new TestCompilerCallbacks(this);
beforeEach(function() {
- languageProcessor = new LanguageProcessor(LMWorker, new TranscriptionCache());
+ languageProcessor = new LanguageProcessor(new NodePredictiveTextWorkerFactory(getPredictiveTextWorkerPath()), new TranscriptionCache());
});
afterEach(function() {
diff --git a/web/src/test/auto/integrated/test_utils.ts b/web/src/test/auto/integrated/test_utils.ts
index 585fac80849..1703388d76b 100644
--- a/web/src/test/auto/integrated/test_utils.ts
+++ b/web/src/test/auto/integrated/test_utils.ts
@@ -13,7 +13,7 @@ export let DEVICE_DETECT_FAILURE = false;
const loc = document.location;
// config.testFile generally starts with a '/', with the path resembling the actual full local
// filesystem for the drive.
-const domain = `${loc.protocol}/${loc.host}`
+const domain = `${loc.protocol}//${loc.host}`;
// If we've set things up to support Device dection without loading KMW...
try {
diff --git a/web/src/test/auto/resources/index.ts b/web/src/test/auto/resources/index.ts
index 7a770a9f3df..4940d95643d 100644
--- a/web/src/test/auto/resources/index.ts
+++ b/web/src/test/auto/resources/index.ts
@@ -17,5 +17,9 @@ export function getKeymanRoot(): string {
}
export function getWebTestResourcesPath(): string {
- return getKeymanRoot() + '/web/src/test/auto/resources';
+ return `${getKeymanRoot()}web/src/test/auto/resources`;
}
+
+export function getPredictiveTextWorkerPath(): string {
+ return `${getKeymanRoot()}web/build/publish/debug/worker-thread.js`;
+}
\ No newline at end of file