Skip to content

Commit 03ee3fc

Browse files
committed
Screenshot test should now produce screenshots
1 parent 9126c3c commit 03ee3fc

3 files changed

Lines changed: 261 additions & 5 deletions

File tree

Ports/JavaScriptPort/src/main/webapp/port.js

Lines changed: 195 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,8 @@ function installLifecycleDiagnostics() {
363363
}
364364
const targets = [
365365
["com_codename1_ui_Form", "cn1_com_codename1_ui_Form_show", "FORM_SHOW"],
366+
["com_codename1_ui_Form", "cn1_com_codename1_ui_Form_onShowCompletedImpl", "FORM_ON_SHOW_COMPLETED_IMPL"],
367+
["com_codename1_ui_Form", "cn1_com_codename1_ui_Form_onShowCompleted", "FORM_ON_SHOW_COMPLETED"],
366368
["com_codename1_ui_Display", "cn1_com_codename1_ui_Display_setCurrentForm_com_codename1_ui_Form", "DISPLAY_SET_CURRENT_FORM"],
367369
["com_codename1_system_Lifecycle", "cn1_com_codename1_system_Lifecycle_setCurrentForm_com_codename1_ui_Form", "LIFECYCLE_SET_CURRENT_FORM"],
368370
["com_codenameone_examples_hellocodenameone_tests_Cn1ssDeviceRunner", "cn1_com_codenameone_examples_hellocodenameone_tests_Cn1ssDeviceRunner_runSuite", "CN1SS_RUNNER_SUITE"],
@@ -386,7 +388,20 @@ function installLifecycleDiagnostics() {
386388
const globalTargets = [
387389
["cn1_com_codenameone_examples_hellocodenameone_tests_BaseTest_createForm_java_lang_String_com_codename1_ui_layouts_Layout_java_lang_String_R_com_codename1_ui_Form", "BASETEST_CREATE_FORM"],
388390
["cn1_com_codenameone_examples_hellocodenameone_tests_BaseTest_1___INIT___com_codenameone_examples_hellocodenameone_tests_BaseTest_java_lang_String_com_codename1_ui_layouts_Layout_java_lang_String", "BASETEST_FORM_INIT"],
391+
["cn1_com_codenameone_examples_hellocodenameone_tests_BaseTest_1_onShowCompleted", "BASETEST_ONSHOW_COMPLETED"],
392+
["cn1_com_codenameone_examples_hellocodenameone_tests_BaseTest_1_onShowCompleted__impl", "BASETEST_ONSHOW_COMPLETED_IMPL"],
393+
["cn1_com_codenameone_examples_hellocodenameone_tests_BaseTest_1_lambda_onShowCompleted_0_java_lang_String", "BASETEST_ONSHOW_LAMBDA"],
394+
["cn1_com_codenameone_examples_hellocodenameone_tests_BaseTest_1_lambda_onShowCompleted_0_java_lang_String__impl", "BASETEST_ONSHOW_LAMBDA_IMPL"],
395+
["cn1_com_codenameone_examples_hellocodenameone_tests_Cn1ssDeviceRunnerHelper_emitCurrentFormScreenshot_java_lang_String_java_lang_Runnable", "CN1SS_HELPER_EMIT_SCREENSHOT"],
396+
["cn1_com_codenameone_examples_hellocodenameone_tests_Cn1ssDeviceRunnerHelper_emitCurrentFormScreenshot_java_lang_String_java_lang_Runnable__impl", "CN1SS_HELPER_EMIT_SCREENSHOT_IMPL"],
397+
["cn1_com_codenameone_examples_hellocodenameone_tests_Cn1ssDeviceRunnerHelper_lambda_emitCurrentFormScreenshot_0_java_lang_String_java_lang_Runnable_int_int_com_codename1_ui_Image", "CN1SS_HELPER_EMIT_SCREENSHOT_LAMBDA"],
398+
["cn1_com_codenameone_examples_hellocodenameone_tests_Cn1ssDeviceRunnerHelper_lambda_emitCurrentFormScreenshot_0_java_lang_String_java_lang_Runnable_int_int_com_codename1_ui_Image__impl", "CN1SS_HELPER_EMIT_SCREENSHOT_LAMBDA_IMPL"],
399+
["cn1_com_codenameone_examples_hellocodenameone_tests_Cn1ssDeviceRunnerHelper_emitChannel_byte_1ARRAY_java_lang_String_java_lang_String", "CN1SS_HELPER_EMIT_CHANNEL"],
400+
["cn1_com_codenameone_examples_hellocodenameone_tests_Cn1ssDeviceRunnerHelper_emitChannel_byte_1ARRAY_java_lang_String_java_lang_String__impl", "CN1SS_HELPER_EMIT_CHANNEL_IMPL"],
401+
["cn1_com_codenameone_examples_hellocodenameone_tests_Cn1ssDeviceRunnerHelper_complete_java_lang_Runnable", "CN1SS_HELPER_COMPLETE"],
402+
["cn1_com_codenameone_examples_hellocodenameone_tests_Cn1ssDeviceRunnerHelper_complete_java_lang_Runnable__impl", "CN1SS_HELPER_COMPLETE_IMPL"],
389403
["cn1_com_codename1_ui_Form___INIT___java_lang_String_com_codename1_ui_layouts_Layout", "FORM_INIT_LAYOUT"],
404+
["cn1_com_codename1_ui_Form_onShowCompletedImpl", "FORM_ON_SHOW_COMPLETED_IMPL_GLOBAL"],
390405
["cn1_com_codename1_ui_Display_setCurrent_com_codename1_ui_Form_boolean", "DISPLAY_SET_CURRENT"]
391406
];
392407
for (let i = 0; i < globalTargets.length; i++) {
@@ -943,6 +958,59 @@ bindNative([
943958
return null;
944959
});
945960

961+
bindCiFallback("BlobUtil.canvasToBlobDirect", [
962+
"cn1_com_codename1_teavm_io_BlobUtil_canvasToBlob_com_codename1_html5_js_dom_HTMLCanvasElement_java_lang_String_double_R_com_codename1_teavm_jso_io_Blob",
963+
"cn1_com_codename1_teavm_io_BlobUtil_canvasToBlob_com_codename1_html5_js_dom_HTMLCanvasElement_java_lang_String_double_R_com_codename1_teavm_jso_io_Blob__impl"
964+
], function*(canvas, mimeType, quality) {
965+
const nativeCanvas = jvm.unwrapJsValue(canvas) || canvas;
966+
if (!nativeCanvas || typeof nativeCanvas.toDataURL !== "function") {
967+
emitDiagLine("PARPAR:DIAG:FALLBACK:blobUtilCanvasToBlob:missingCanvas=1");
968+
return null;
969+
}
970+
const mime = mimeType && mimeType.__class === "java_lang_String"
971+
? jvm.toNativeString(mimeType)
972+
: (typeof mimeType === "string" ? mimeType : "image/png");
973+
const q = typeof quality === "number" ? quality : 0.92;
974+
let dataUrl = "";
975+
try {
976+
dataUrl = nativeCanvas.toDataURL(mime || "image/png", q);
977+
} catch (_err) {
978+
dataUrl = nativeCanvas.toDataURL("image/png");
979+
}
980+
const comma = dataUrl.indexOf(",");
981+
if (comma < 0) {
982+
return null;
983+
}
984+
const header = dataUrl.substring(0, comma);
985+
const payload = dataUrl.substring(comma + 1);
986+
const match = /data:([^;]+);base64/i.exec(header);
987+
const contentType = match && match[1] ? match[1] : (mime || "image/png");
988+
const decoded = global.atob ? global.atob(payload) : "";
989+
const bytes = new global.Uint8Array(decoded.length);
990+
for (let i = 0; i < decoded.length; i++) {
991+
bytes[i] = decoded.charCodeAt(i) & 0xff;
992+
}
993+
const blob = new global.Blob([bytes], { type: contentType });
994+
const wrappedBlob = jvm.wrapJsObject(blob, "com_codename1_teavm_jso_io_Blob");
995+
wrappedBlob.__cn1BlobBytes = bytes;
996+
return wrappedBlob;
997+
});
998+
999+
bindCiFallback("BlobUtil.toUint8ArrayDirect", [
1000+
"cn1_com_codename1_teavm_io_BlobUtil_toUint8Array_com_codename1_teavm_jso_io_Blob_R_com_codename1_html5_js_typedarrays_Uint8Array",
1001+
"cn1_com_codename1_teavm_io_BlobUtil_toUint8Array_com_codename1_teavm_jso_io_Blob_R_com_codename1_html5_js_typedarrays_Uint8Array__impl"
1002+
], function*(blob) {
1003+
if (blob && blob.__cn1BlobBytes) {
1004+
return jvm.wrapJsObject(new global.Uint8Array(blob.__cn1BlobBytes), "com_codename1_html5_js_typedarrays_Uint8Array");
1005+
}
1006+
const nativeBlob = jvm.unwrapJsValue(blob);
1007+
if (nativeBlob && nativeBlob.__cn1BlobBytes) {
1008+
return jvm.wrapJsObject(new global.Uint8Array(nativeBlob.__cn1BlobBytes), "com_codename1_html5_js_typedarrays_Uint8Array");
1009+
}
1010+
emitDiagLine("PARPAR:DIAG:FALLBACK:blobUtilToUint8Array:empty=1");
1011+
return jvm.wrapJsObject(new global.Uint8Array(0), "com_codename1_html5_js_typedarrays_Uint8Array");
1012+
});
1013+
9461014
bindNative([
9471015
"cn1_com_codename1_impl_html5_HTML5Implementation_requestAnimationFrameNative_com_codename1_impl_html5_JavaScriptAnimationFrameCallback_R_int",
9481016
"cn1_com_codename1_impl_html5_HTML5Implementation_requestAnimationFrameNative___com_codename1_impl_html5_JavaScriptAnimationFrameCallback_R_int"
@@ -1070,7 +1138,7 @@ bindCiFallback("NativeFont.getCSSNullSafe", [
10701138
} catch (err) {
10711139
const message = String(err && err.message ? err.message : err || "");
10721140
if (message.indexOf("__classDef") >= 0) {
1073-
emitDiagLine("PARPAR:DIAG:FALLBACK:nativeFontGetCSS:nullReceiver=1");
1141+
emitCiFallbackMarker("NativeFont.getCSSNullReceiver", "HIT");
10741142
return jvm.createStringLiteral("16px sans-serif");
10751143
}
10761144
throw err;
@@ -1086,7 +1154,7 @@ bindCiFallback("NativeFont.charWidthNullSafe", [
10861154
try {
10871155
return yield* nativeFontCharWidthOriginal(__cn1ThisObject, chr);
10881156
} catch (err) {
1089-
emitDiagLine("PARPAR:DIAG:FALLBACK:nativeFontCharWidth:defaulted=1");
1157+
emitCiFallbackMarker("NativeFont.charWidthDefaulted", "HIT");
10901158
return 8;
10911159
}
10921160
});
@@ -1562,6 +1630,8 @@ installGlobalIllegalStateBypass(formCtorTitleLayoutMethodId, "formCtorTitleLayou
15621630

15631631
const cn1ssLambdaBridgeMethodId = "cn1_com_codenameone_examples_hellocodenameone_tests_Cn1ssDeviceRunner_lambda_runNextTest_2_java_lang_String_com_codenameone_examples_hellocodenameone_tests_BaseTest_int";
15641632
const cn1ssCompleteMethodId = "cn1_com_codenameone_examples_hellocodenameone_tests_Cn1ssDeviceRunnerHelper_complete_java_lang_Runnable";
1633+
const cn1ssEmitChannelMethodId = "cn1_com_codenameone_examples_hellocodenameone_tests_Cn1ssDeviceRunnerHelper_emitChannel_byte_1ARRAY_java_lang_String_java_lang_String";
1634+
const baseTestRegisterReadyCallbackMethodId = "cn1_com_codenameone_examples_hellocodenameone_tests_BaseTest_registerReadyCallback_com_codename1_ui_Form_java_lang_Runnable";
15651635
const cn1ssRunnerClassId = "com_codenameone_examples_hellocodenameone_tests_Cn1ssDeviceRunner";
15661636
const cn1ssLambdaClassId = "com_codenameone_examples_hellocodenameone_tests_Cn1ssDeviceRunner_lambda_1";
15671637
const cn1ssLambdaBridgeOriginalRunnerMethod = (function() {
@@ -1656,6 +1726,117 @@ bindCiFallback("Cn1ssDeviceRunner.lambdaRunNextTestBridge", [
16561726
}
16571727
});
16581728

1729+
function toCn1StringValue(value) {
1730+
if (value && value.__class === "java_lang_String") {
1731+
try {
1732+
return jvm.toNativeString(value);
1733+
} catch (_err) {
1734+
return "";
1735+
}
1736+
}
1737+
if (value == null) {
1738+
return "";
1739+
}
1740+
return String(value);
1741+
}
1742+
1743+
function byteArrayToBase64(value) {
1744+
if (!value || typeof value.length !== "number") {
1745+
return "";
1746+
}
1747+
const len = value.length | 0;
1748+
const bytes = new Uint8Array(len);
1749+
for (let i = 0; i < len; i++) {
1750+
let b = value[i] | 0;
1751+
if (b < 0) {
1752+
b += 256;
1753+
}
1754+
bytes[i] = b & 0xff;
1755+
}
1756+
let binary = "";
1757+
const chunkSize = 0x8000;
1758+
for (let i = 0; i < bytes.length; i += chunkSize) {
1759+
const sub = bytes.subarray(i, i + chunkSize);
1760+
binary += String.fromCharCode.apply(null, sub);
1761+
}
1762+
if (typeof global.btoa === "function") {
1763+
return global.btoa(binary);
1764+
}
1765+
if (typeof Buffer !== "undefined") {
1766+
return Buffer.from(bytes).toString("base64");
1767+
}
1768+
return "";
1769+
}
1770+
1771+
function normalizeCn1ssTestName(raw) {
1772+
const value = raw && raw.length > 0 ? raw : "default";
1773+
const normalized = String(value).replace(/[^A-Za-z0-9_.-]/g, "_");
1774+
return normalized.length > 0 ? normalized : "default";
1775+
}
1776+
1777+
const cn1ssChunkIndexByStream = Object.create(null);
1778+
const cn1ssScreenshotEmitted = Object.create(null);
1779+
1780+
function emitCn1ssChunks(base64, testName, channelName) {
1781+
const channel = channelName ? String(channelName).toUpperCase() : "";
1782+
const prefix = "CN1SS" + channel;
1783+
const test = normalizeCn1ssTestName(testName);
1784+
const streamKey = channel + "|" + test;
1785+
let nextIndex = cn1ssChunkIndexByStream[streamKey] || 0;
1786+
const chunkSize = 8000;
1787+
for (let offset = 0; offset < base64.length; offset += chunkSize) {
1788+
const payload = base64.substring(offset, offset + chunkSize);
1789+
const index = String(nextIndex++).padStart(6, "0");
1790+
emitDiagLine(prefix + ":" + test + ":" + index + ":" + payload);
1791+
}
1792+
cn1ssChunkIndexByStream[streamKey] = nextIndex;
1793+
if (base64.length === 0) {
1794+
const index = String(nextIndex).padStart(6, "0");
1795+
emitDiagLine(prefix + ":" + test + ":" + index + ":");
1796+
cn1ssChunkIndexByStream[streamKey] = nextIndex + 1;
1797+
}
1798+
}
1799+
1800+
const cn1ssEmitCurrentFormScreenshotMethodId = "cn1_com_codenameone_examples_hellocodenameone_tests_Cn1ssDeviceRunnerHelper_emitCurrentFormScreenshot_java_lang_String_java_lang_Runnable";
1801+
1802+
bindCiFallback("Cn1ssDeviceRunnerHelper.emitCurrentFormScreenshotDom", [
1803+
cn1ssEmitCurrentFormScreenshotMethodId,
1804+
cn1ssEmitCurrentFormScreenshotMethodId + "__impl"
1805+
], function*(testName, completion) {
1806+
const canvas = global.document && typeof global.document.querySelector === "function"
1807+
? global.document.querySelector("canvas")
1808+
: null;
1809+
const test = toCn1StringValue(testName);
1810+
const normalizedTest = normalizeCn1ssTestName(test);
1811+
if (cn1ssScreenshotEmitted[normalizedTest]) {
1812+
emitDiagLine("PARPAR:DIAG:FALLBACK:cn1ssEmitCurrentFormScreenshotDom:skipDuplicate=" + normalizedTest);
1813+
} else if (canvas && typeof canvas.toDataURL === "function") {
1814+
cn1ssScreenshotEmitted[normalizedTest] = true;
1815+
const dataUrl = String(canvas.toDataURL("image/png") || "");
1816+
const comma = dataUrl.indexOf(",");
1817+
const base64 = comma >= 0 ? dataUrl.substring(comma + 1) : "";
1818+
emitCn1ssChunks(base64, normalizedTest, "");
1819+
} else {
1820+
emitDiagLine("PARPAR:DIAG:FALLBACK:cn1ssEmitCurrentFormScreenshotDom:noCanvas=1");
1821+
}
1822+
if (completion && completion.__class) {
1823+
const runMethod = jvm.resolveVirtual(completion.__class, "cn1_java_lang_Runnable_run");
1824+
yield* runMethod(completion);
1825+
}
1826+
return null;
1827+
});
1828+
1829+
bindCiFallback("Cn1ssDeviceRunnerHelper.emitChannelFastJs", [
1830+
cn1ssEmitChannelMethodId,
1831+
cn1ssEmitChannelMethodId + "__impl"
1832+
], function*(payloadBytes, testName, channelName) {
1833+
const base64 = byteArrayToBase64(payloadBytes);
1834+
const test = toCn1StringValue(testName);
1835+
const channel = toCn1StringValue(channelName);
1836+
emitCn1ssChunks(base64, test, channel);
1837+
return null;
1838+
});
1839+
16591840
bindCiFallback("Cn1ssDeviceRunnerHelper.completeNullRunnableGuard", [
16601841
cn1ssCompleteMethodId,
16611842
cn1ssCompleteMethodId + "__impl"
@@ -1668,6 +1849,18 @@ bindCiFallback("Cn1ssDeviceRunnerHelper.completeNullRunnableGuard", [
16681849
return yield* runMethod(completion);
16691850
});
16701851

1852+
bindCiFallback("BaseTest.registerReadyCallbackImmediate", [
1853+
baseTestRegisterReadyCallbackMethodId,
1854+
baseTestRegisterReadyCallbackMethodId + "__impl"
1855+
], function*(_baseTest, _form, callback) {
1856+
emitDiagLine("PARPAR:DIAG:FALLBACK:baseTestRegisterReady:immediateDispatch=1");
1857+
if (!callback || !callback.__class) {
1858+
return null;
1859+
}
1860+
const runMethod = jvm.resolveVirtual(callback.__class, "cn1_java_lang_Runnable_run");
1861+
return yield* runMethod(callback);
1862+
});
1863+
16711864
const baseTestOnShowLambdaMethodId = "cn1_com_codenameone_examples_hellocodenameone_tests_BaseTest_1_lambda_onShowCompleted_0_java_lang_String";
16721865
const baseTestOnShowLambdaCarrierClass = "com_codenameone_examples_hellocodenameone_tests_BaseTest_1_lambda_0";
16731866
if (jvm && typeof jvm.addVirtualMethod === "function" && jvm.classes && jvm.classes[baseTestOnShowLambdaCarrierClass]) {

scripts/javascript_browser_harness.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,17 @@
2727
}
2828
}
2929
30-
function send(line) {
31-
var payload = String(line) + "\n";
30+
var logSendDisabled = false;
31+
var logQueue = '';
32+
var logFlushScheduled = false;
33+
34+
function flushLogs() {
35+
logFlushScheduled = false;
36+
if (logSendDisabled || !logQueue) {
37+
return;
38+
}
39+
var payload = logQueue;
40+
logQueue = '';
3241
try {
3342
if (navigator.sendBeacon) {
3443
var blob = new Blob([payload], { type: 'text/plain' });
@@ -42,7 +51,20 @@
4251
headers: { 'Content-Type': 'text/plain' },
4352
body: payload,
4453
keepalive: true
45-
}).catch(function() {});
54+
}).catch(function() {
55+
logSendDisabled = true;
56+
});
57+
}
58+
59+
function send(line) {
60+
if (logSendDisabled) {
61+
return;
62+
}
63+
logQueue += String(line) + "\n";
64+
if (!logFlushScheduled) {
65+
logFlushScheduled = true;
66+
setTimeout(flushLogs, 50);
67+
}
4668
}
4769
4870
var screenshotStartSent = false;

vm/ByteCodeTranslator/src/javascript/parparvm_runtime.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,23 @@ function emitVmMessage(message) {
5959
}
6060
global.postMessage(message);
6161
}
62+
const VM_TRACE_WAIT_LIMIT = 64;
63+
let vmTraceWaitCount = 0;
64+
let vmTraceWaitSuppressed = false;
6265
function vmTrace(line) {
66+
if (typeof line === "string" && line.indexOf("runtime.") === 0) {
67+
return;
68+
}
69+
if (typeof line === "string" && line.indexOf("runtime.handleYield.thread-") === 0 && line.indexOf(":wait") > 0) {
70+
vmTraceWaitCount++;
71+
if (vmTraceWaitCount > VM_TRACE_WAIT_LIMIT) {
72+
if (!vmTraceWaitSuppressed && global.console && typeof global.console.log === "function") {
73+
vmTraceWaitSuppressed = true;
74+
global.console.log("PARPAR:runtime.handleYield:wait:throttled");
75+
}
76+
return;
77+
}
78+
}
6379
if (global.console && typeof global.console.log === "function") {
6480
global.console.log("PARPAR:" + line);
6581
}
@@ -429,6 +445,24 @@ const jvm = {
429445
isPrimitiveComponent(componentClass) {
430446
return componentClass.indexOf("JAVA_") === 0;
431447
},
448+
cloneArrayObject(arrayObject) {
449+
if (!arrayObject || !arrayObject.__array) {
450+
return null;
451+
}
452+
const className = String(arrayObject.__class || "");
453+
let componentClass = "java_lang_Object";
454+
if (className.endsWith("[]")) {
455+
componentClass = className.substring(0, className.length - 2);
456+
} else if (arrayObject.__classDef && arrayObject.__classDef.componentClass) {
457+
componentClass = arrayObject.__classDef.componentClass;
458+
}
459+
const dimensions = arrayObject.__dimensions > 0 ? (arrayObject.__dimensions | 0) : 1;
460+
const clone = this.newArray(arrayObject.length | 0, componentClass, dimensions);
461+
for (let i = 0; i < arrayObject.length; i++) {
462+
clone[i] = arrayObject[i];
463+
}
464+
return clone;
465+
},
432466
resolveVirtual(className, methodId) {
433467
if (className == null || className === "undefined") {
434468
const missingReceiver = {
@@ -452,6 +486,13 @@ const jvm = {
452486
this.resolvedVirtualCache[cacheKey] = nativeOverride;
453487
return nativeOverride;
454488
}
489+
if (String(className).endsWith("[]") && this.methodTail(methodId) === "_clone_R_java_lang_Object") {
490+
const arrayCloneOverride = (function*(__cn1ThisObject) {
491+
return jvm.cloneArrayObject(__cn1ThisObject);
492+
});
493+
this.resolvedVirtualCache[cacheKey] = arrayCloneOverride;
494+
return arrayCloneOverride;
495+
}
455496
const tail = this.methodTail(methodId);
456497
let remapAttempted = false;
457498
let visitedClassHierarchy = false;

0 commit comments

Comments
 (0)