Skip to content

Compose as a new platform #234

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kotlin-playground",
"version": "1.32.0",
"version": "1.32.0-alpha.3",
"description": "Self-contained component to embed in websites for running Kotlin code",
"keywords": [
"kotlin",
Expand Down
3 changes: 0 additions & 3 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ export const API_URLS = {
url = `${this.server}/api/${version}/compiler/translate`;
break;
case TargetPlatforms.JS:
url = `${this.server}/api/${version}/compiler/translate`;
break;
case TargetPlatforms.JS_IR:
url = `${this.server}/api/${version}/compiler/translate?ir=true`;
break;
case TargetPlatforms.WASM:
Expand Down
4 changes: 3 additions & 1 deletion src/executable-code/executable-fragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,8 @@ export default class ExecutableFragment extends ExecutableCodeTemplate {
}

onConsoleCloseButtonEnter() {
const { jsLibs, onCloseConsole, targetPlatform, compilerVersion } = this.state;
const { jsLibs, onCloseConsole, targetPlatform, compilerVersion } =
this.state;
// creates a new iframe and removes the old one, thereby stops execution of any running script
if (isJsRelated(targetPlatform) || isWasmRelated(targetPlatform))
this.jsExecutor.reloadIframeScripts(
Expand Down Expand Up @@ -454,6 +455,7 @@ export default class ExecutableFragment extends ExecutableCodeTemplate {
theme,
onError,
additionalRequestsResults,
compilerVersion,
)
.then((output) => {
const originState = state.openConsole;
Expand Down
225 changes: 131 additions & 94 deletions src/js-executor/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import './index.scss';
import {API_URLS} from '../config';
import {showJsException} from '../view/output-view';
import {processingHtmlBrackets} from '../utils';
import {isWasmRelated, TargetPlatforms} from '../utils/platforms';
import {executeJs, executeWasmCode, executeWasmCodeWithSkiko, executeWasmCodeWithStdlib} from './execute-es-module';
import {fetch} from "whatwg-fetch";
import { API_URLS } from '../config';
import { showJsException } from '../view/output-view';
import { processingHtmlBrackets } from '../utils';
import { isJsLegacy, isWasmRelated, TargetPlatforms } from '../utils/platforms';
import {
executeJs,
executeWasmCode,
executeWasmCodeWithSkiko,
executeWasmCodeWithStdlib,
} from './execute-es-module';
import { fetch } from 'whatwg-fetch';

const INIT_SCRIPT =
'if(kotlin.BufferedOutput!==undefined){kotlin.out = new kotlin.BufferedOutput()}' +
Expand Down Expand Up @@ -38,9 +43,10 @@ export default class JsExecutor {
theme,
onError,
additionalRequestsResults,
compilerVersion,
) {
if (platform === TargetPlatforms.SWIFT_EXPORT) {
return `<span class="standard-output ${theme}"><div class="result-code">${jsCode}</span>`;
return `<span class='standard-output ${theme}'><div class='result-code'>${jsCode}</span>`;
}
if (platform === TargetPlatforms.CANVAS) {
this.iframe.style.display = 'block';
Expand Down Expand Up @@ -90,59 +96,78 @@ export default class JsExecutor {
return result;
}
}
return await this.execute(jsCode, jsLibs, theme, onError, platform);
return await this.execute(
jsCode,
jsLibs,
theme,
onError,
platform,
compilerVersion,
);
}

async execute(jsCode, jsLibs, theme, onError, platform) {
async execute(jsCode, jsLibs, theme, onError, platform, compilerVersion) {
const loadedScripts = (
this.iframe.contentDocument || this.iframe.document
).getElementsByTagName('script').length;
let offset;
if (platform === TargetPlatforms.JS_IR) {
// 1 scripts by default: INIT_SCRIPT_IR
offset = 1;
} else {
let offset = 1; // 1 scripts by default: INIT_SCRIPT_IR
if (isJsLegacy(platform, compilerVersion)) {
// 2 scripts by default: INIT_SCRIPT + kotlin stdlib
offset = 2;
}
if (loadedScripts === jsLibs.size + offset) {
try {
const output = this.iframe.contentWindow.eval(jsCode);
return output
? `<span class="standard-output ${theme}">${processingHtmlBrackets(
output,
)}</span>`
? `<span class='standard-output ${theme}'>${processingHtmlBrackets(
output,
)}</span>`
: '';
} catch (e) {
if (onError) onError();
let exceptionOutput = showJsException(e);
return `<span class="error-output">${exceptionOutput}</span>`;
return `<span class='error-output'>${exceptionOutput}</span>`;
}
}
await this.timeout(400);
return await this.execute(jsCode, jsLibs, theme, onError, platform);
return await this.execute(
jsCode,
jsLibs,
theme,
onError,
platform,
compilerVersion,
);
}

async executeWasm(jsCode, wasmCode, executor, theme, onError, imports, output) {
async executeWasm(
jsCode,
wasmCode,
executor,
theme,
onError,
imports,
output,
) {
try {
const exports = await executor(
this.iframe.contentWindow,
jsCode,
wasmCode,
);
await exports.instantiate({"playground.master": imports});
await exports.instantiate({ 'playground.master': imports });
const bufferedOutput = output ?? exports.bufferedOutput;
const outputString = bufferedOutput.buffer;
bufferedOutput.buffer = '';
return outputString
? `<span class="standard-output ${theme}">${processingHtmlBrackets(
outputString,
)}</span>`
? `<span class='standard-output ${theme}'>${processingHtmlBrackets(
outputString,
)}</span>`
: '';
} catch (e) {
if (onError) onError();
let exceptionOutput = showJsException(e);
return `<span class="error-output">${exceptionOutput}</span>`;
return `<span class='error-output'>${exceptionOutput}</span>`;
}
}

Expand All @@ -159,10 +184,7 @@ export default class JsExecutor {
node.appendChild(this.iframe);
let iframeDoc = this.iframe.contentDocument || this.iframe.document;
iframeDoc.open();
if (
targetPlatform === TargetPlatforms.JS ||
targetPlatform === TargetPlatforms.CANVAS
) {
if (isJsLegacy(targetPlatform, compilerVersion)) {
const kotlinScript =
API_URLS.KOTLIN_JS +
`${normalizeJsVersion(this.kotlinVersion)}/kotlin.js`;
Expand All @@ -175,85 +197,86 @@ export default class JsExecutor {
for (let lib of jsLibs) {
iframeDoc.write("<script src='" + lib + "'></script>");
}
if (targetPlatform === TargetPlatforms.JS_IR) {
iframeDoc.write(`<script>${INIT_SCRIPT_IR}</script>`);
} else {
iframeDoc.write(`<script>${INIT_SCRIPT}</script>`);
}
iframeDoc.write(
`<script>${isJsLegacy(targetPlatform, compilerVersion) ? INIT_SCRIPT : INIT_SCRIPT_IR}</script>`,
);
}
if (targetPlatform === TargetPlatforms.COMPOSE_WASM) {

const skikoStdlib = fetch(API_URLS.RESOURCE_VERSIONS(),{
method: 'GET'
}).then(response => response.json())
.then(versions => {
const skikoVersion = versions["skiko"];
const skikoStdlib = fetch(API_URLS.RESOURCE_VERSIONS(), {
method: 'GET',
})
.then((response) => response.json())
.then((versions) => {
const skikoVersion = versions['skiko'];

const skikoExports = fetch(API_URLS.SKIKO_MJS(skikoVersion), {
method: 'GET',
headers: {
'Content-Type': 'text/javascript',
}
}).then(script => script.text())
.then(script => script.replace(
"new URL(\"skiko.wasm\",import.meta.url).href",
`'${API_URLS.SKIKO_WASM(skikoVersion)}'`
))
.then(skikoCode =>
executeJs(
this.iframe.contentWindow,
skikoCode,
))
.then(skikoExports => fixedSkikoExports(skikoExports))
},
})
.then((script) => script.text())
.then((script) =>
script.replace(
'new URL("skiko.wasm",import.meta.url).href',
`'${API_URLS.SKIKO_WASM(skikoVersion)}'`,
),
)
.then((skikoCode) =>
executeJs(this.iframe.contentWindow, skikoCode),
)
.then((skikoExports) => fixedSkikoExports(skikoExports));

const stdlibVersion = versions["stdlib"];
const stdlibVersion = versions['stdlib'];

const stdlibExports = fetch(API_URLS.STDLIB_MJS(stdlibVersion), {
method: 'GET',
headers: {
'Content-Type': 'text/javascript',
}
}).then(script => script.text())
.then(script =>
},
})
.then((script) => script.text())
.then((script) =>
// necessary to load stdlib.wasm before its initialization to parallelize
// language=JavaScript
(`const stdlibWasm = fetch('${API_URLS.STDLIB_WASM(stdlibVersion)}');\n` + script).replace(
"fetch(new URL('./stdlib_master.wasm',import.meta.url).href)",
"stdlibWasm"
).replace(
"(extends) => { return { extends }; }",
"(extends_) => { return { extends_ }; }"
))
.then(stdlibCode =>
executeWasmCodeWithSkiko(
this.iframe.contentWindow,
stdlibCode,
(
`const stdlibWasm = fetch('${API_URLS.STDLIB_WASM(stdlibVersion)}'); ` +
script
)
.replace(
"fetch(new URL('./stdlib_master.wasm',import.meta.url).href)",
'stdlibWasm',
)
.replace(
'(extends) => { return { extends }; }',
'(extends_) => { return { extends_ }; }',
),
)
.then((stdlibCode) =>
executeWasmCodeWithSkiko(this.iframe.contentWindow, stdlibCode),
);

return Promise.all([skikoExports, stdlibExports])
})
return Promise.all([skikoExports, stdlibExports]);
});

this.stdlibExports = skikoStdlib
.then(async ([skikoExportsResult, stdlibExportsResult]) => {
return [
await stdlibExportsResult.instantiate({
"./skiko.mjs": skikoExportsResult
}),
stdlibExportsResult
]
}
)
return [
await stdlibExportsResult.instantiate({
'./skiko.mjs': skikoExportsResult,
}),
stdlibExportsResult,
];
})
.then(([stdlibResult, outputResult]) => {
return {
"stdlib": stdlibResult.exports,
"output": outputResult.bufferedOutput
}
}
)
return {
stdlib: stdlibResult.exports,
output: outputResult.bufferedOutput,
};
});

this.iframe.height = "1000"
iframeDoc.write(`<canvas height="1000" id="ComposeTarget"></canvas>`);
this.iframe.height = '1000';
iframeDoc.write(`<canvas height='1000' id='ComposeTarget'></canvas>`);
}
iframeDoc.write('<body style="margin: 0; overflow: hidden;"></body>');
iframeDoc.close();
Expand All @@ -264,25 +287,39 @@ function fixedSkikoExports(skikoExports) {
return {
...skikoExports,
org_jetbrains_skia_Bitmap__1nGetPixmap: function () {
console.log("org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer")
console.log(
'org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer',
);
},
org_jetbrains_skia_Bitmap__1nIsVolatile: function () {
console.log("org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer")
console.log(
'org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer',
);
},
org_jetbrains_skia_Bitmap__1nSetVolatile: function () {
console.log("org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer")
console.log(
'org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer',
);
},
org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer: function () {
console.log("org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer")
console.log(
'org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer',
);
},
org_jetbrains_skia_TextBlobBuilderRunHandler__1nMake: function () {
console.log("org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer")
console.log(
'org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer',
);
},
org_jetbrains_skia_TextBlobBuilderRunHandler__1nMakeBlob: function () {
console.log("org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer")
console.log(
'org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer',
);
},
org_jetbrains_skia_svg_SVGCanvasKt__1nMake: function () {
console.log("org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer")
}
}
console.log(
'org_jetbrains_skia_TextBlobBuilderRunHandler__1nGetFinalizer',
);
},
};
}
1 change: 0 additions & 1 deletion src/utils/platforms/TargetPlatforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import TargetPlatform from './TargetPlatform';

export const TargetPlatforms = {
JS: new TargetPlatform('js', 'JavaScript'),
JS_IR: new TargetPlatform('js-ir', 'JavaScript IR'),
WASM: new TargetPlatform('wasm', 'Wasm'),
COMPOSE_WASM: new TargetPlatform('compose-wasm', 'Compose Wasm'),
JAVA: new TargetPlatform('java', 'JVM'),
Expand Down
Loading