Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,22 @@
<div id="status-notice"></div>
</div>

<script>
(function () {
var _origError = console.error.bind(console);
console.error = function () {
_origError.apply(console, arguments);
var text = Array.prototype.join.call(arguments, ' ');
var target = window.hostOrigin || '*';
window.parent.postMessage({
source: 'godot-activecode',
subject: 'runestone',
type: 'error',
message: text
}, target);
};
}());
</script>
<script src="index.js"></script>
<script>
const GODOT_CONFIG = {"args":[],"canvasResizePolicy":2,"emscriptenPoolSize":8,"ensureCrossOriginIsolationHeaders":false,"executable":"index","experimentalVK":false,"fileSizes":{"index.pck":1497672,"index.wasm":35754592},"focusCanvas":true,"gdextensionLibs":[],"godotPoolSize":4};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8769,13 +8769,16 @@ var Godot = (() => {

const known_layout_pattern = /^window\.parent\.postMessage\((\{.*\}), ['](.*?)[']\);$/s;
const gShellPattern = /^window\.parent\.postMessage\(\{["]source["]:["]godot-activecode["],["]subject["]:["]runestone["],["]type["]:["]ready["]\}, ['][*][']\);$/s;
const firstPostPattern = /^\s*if \(typeof window\.hostOrigin === [']undefined[']\) \{\n\s*window\.hostOrigin = null;\n\s*\}\n\s*\n\s*window\.addEventListener\('message', \(event\) => \{\n\s*if \(!window\.hostOrigin && event\.data && event\.data\.type === [']loadExercise[']\) \{\n\s*window\.hostOrigin = event\.origin;\n\s*}\n\s*\n\s*if \(event\.origin !== window\.hostOrigin\) return;\n\s*\n\s*if \(event\.data && event\.data\.type === [']loadExercise[']\) \{\n\s*if \(window\.godotLoadExerciseCallback\) \{\n\s*window\.godotLoadExerciseCallback\(event\.data\.payload\);\n\s*\}\n\s*\}\n\s+\}\);\n?\s*$/s
const firstPostPattern = /^\s*if \(typeof window\.hostOrigin === [']undefined[']\) \{\n\s*window\.hostOrigin = null;\n\s*\}\n\s*\n\s*window\.addEventListener\('message', \(event\) => \{\n\s*if \(!window\.hostOrigin && event\.data && event\.data\.type === [']loadExercise[']\) \{\n\s*window\.hostOrigin = event\.origin;\n\s*}\n\s*\n\s*if \(event\.origin !== window\.hostOrigin\) return;\n\s*\n\s*if \(event\.data && event\.data\.type === [']loadExercise[']\) \{\n\s*if \(window\.godotLoadExerciseCallback\) \{\n\s*window\.godotLoadExerciseCallback\(event\.data\.payload\);\n\s*\}\n\s*\}\n\s+\}\);\n?\s*$/s
const printCapturePattern = /^\s*\(function\(\) \{\n\s+var _origLog = console\.log\.bind\(console\);\n\s+console\.log = function\(\) {\n\s+_origLog\.apply\(console, arguments\);\n\s+var text = Array\.prototype\.join\.call\(arguments, ['] [']\);\n\s+\n\s+var target = window\.hostOrigin \|\| ['][*]['];\n\s+\n\s+window\.parent\.postMessage\(\{\n\s+source: [']godot-activecode['],\n\s+subject: [']runestone['],\n\s+type: [']print['],\n\s+text: text\s+\}, target\);\n\s+};\n\s+\}\)\(\);\n?\s*$/s
const match = js_code.match(known_layout_pattern);
const errorCapturePattern = /^\s*\(function\(\) \{\n\s+var _origError = console\.error\.bind\(console\);\n\s+console\.error = function\(\) {\n\s+_origError\.apply\(console, arguments\);\n\s+var text = Array\.prototype\.join\.call\(arguments, ['] [']\);\n\s+\n\s+var target = window\.hostOrigin \|\| ['][*]['];\n\s+\n\s+window\.parent\.postMessage\(\{\n\s+source: [']godot-activecode['],\n\s+subject: [']runestone['],\n\s+type: [']error['],\n\s+message: text\s+\}, target\);\n\s+};\n\s+\}\)\(\);\n?\s*$/s
const match = js_code.match(known_layout_pattern);
const is_godot_shell = js_code.match(gShellPattern);
const is_first_pattern = js_code.match(firstPostPattern);
const is_print_capture_pattern = js_code.match(printCapturePattern);
if (is_godot_shell || is_first_pattern || is_print_capture_pattern) {
const is_error_capture_pattern = js_code.match(errorCapturePattern);
if (is_godot_shell || is_first_pattern || is_print_capture_pattern
|| is_error_capture_pattern) {
// pass through
} else if (!match) {
console.warn("Security Drop: java script not executed: ", js_code);
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ export default class GodotActiveCode extends ActiveCode {
// Buffer for print() lines received before or between result messages.
// Cleared at the start of each run and displayed in this.output
// alongside the final summary.
this._printLines = [];
this._outputLines = [];

this._createPlaceholder();

Expand Down Expand Up @@ -366,11 +366,11 @@ export default class GodotActiveCode extends ActiveCode {
// print() output captured during the run, separated from
// the summary line by a blank line.
var summaryLine = $(this.output).text().toLowerCase().includes("error") ? $(this.output).text() : "No Errors";
var outputText = this._printLines.length > 0
? this._printLines.join("\n") + "\n\n" + summaryLine
: summaryLine;
$(this.output).text(outputText);
var formatted = this._formatOutputLines();
$(this.output).text(formatted.lines.join("\n"));
$(this.output).css("visibility", "visible");
$(this.output).toggleClass("error", formatted.hasError);


// Store unit_results in the same format Runestone expects for
// unittest events (matches Python/SQL grade passback format).
Expand Down Expand Up @@ -496,28 +496,46 @@ export default class GodotActiveCode extends ActiveCode {
text.startsWith("Progress saved") ||
text.startsWith("vram")) return;

this._printLines.push(text);
this._outputLines.push({ type: "print", text: text });

// Show print output immediately as it arrives, so students see it
// even before the result comes back (e.g. during a long sample window).
var current = $(this.output).text();
$(this.output).text(
current ? current + "\n" + text : text
);
$(this.output).css("visibility", "visible");
this._renderOutput();
}

// -------------------------------------------------------------------------
// Called when the shell posts { type: "error", message }.
// -------------------------------------------------------------------------
_onError(message) {
$(this.output).text("Error: " + message);
$(this.output).css("visibility", "visible");
$(this.output).addClass("error");
if (typeof message !== "string") return;
if (!message.startsWith("SCRIPT ERROR:")) return;

// Hide any stale results table from a previous run.
this.unitResultsDiv.style.display = "none";
this.unitResultsDiv.innerHTML = "";
this._outputLines.push({ type: "error", text: message });
this._renderOutput();
}

// -------------------------------------------------------------------------
// Shared line-formatting logic used by both the live view (_renderOutput)
// and the final summary (_onResult). Builds a flat array of display lines
// from _outputLines, in arrival order, with errors prefixed. If there were
// no errors at all, appends a trailing "No Errors" confirmation line.
// -------------------------------------------------------------------------
_formatOutputLines() {
var lines = this._outputLines.map(function(entry) {
return entry.type === "error" ? "Error: " + entry.text : entry.text;
});
var hasError = this._outputLines.some(function(e) { return e.type === "error"; });
if (!hasError) {
lines.push("No Errors");
}
return { lines: lines, hasError: hasError };
}

_renderOutput() {
var formatted = this._formatOutputLines();
$(this.output).text(formatted.lines.join("\n"));
$(this.output).css("visibility", "visible");
$(this.output).toggleClass("error", formatted.hasError);
}

// -------------------------------------------------------------------------
Expand All @@ -537,7 +555,7 @@ export default class GodotActiveCode extends ActiveCode {
$(this.output).removeClass("error");
this.unitResultsDiv.innerHTML = "";
this.unitResultsDiv.style.display = "none";
this._printLines = [];
this._outputLines = [];

// Get the student's code from the CodeMirror editor.
var studentCode = this.editor.getValue();
Expand Down