116116 Object.extend(Squeak,
117117 "version", {
118118 // system attributes
119- vmVersion: "SqueakJS 1.2.1 ",
120- vmDate: "2024-05-27 ", // Maybe replace at build time?
119+ vmVersion: "SqueakJS 1.2.2 ",
120+ vmDate: "2024-06-22 ", // Maybe replace at build time?
121121 vmBuild: "unknown", // or replace at runtime by last-modified?
122122 vmPath: "unknown", // Replace at runtime
123123 vmFile: "vm.js",
49124912 },
49134913 stObjectatput: function(array, index, obj) {
49144914 if (array.sqClass !== this.classArray()) throw Error("Array expected");
4915- if (index < 1 || index >= array.pointers.length) return this.successFlag = false;
4916- array.pointers[index] = obj;
4915+ if (index < 1 || index > array.pointers.length) return this.successFlag = false;
4916+ array.pointers[index-1 ] = obj;
49174917 },
49184918 },
49194919 'constant access',
@@ -10071,14 +10071,39 @@
1007110071 Object.extend(Squeak.Primitives.prototype,
1007210072 'input', {
1007310073 primitiveClipboardText: function(argCount) {
10074+ // There are two ways this primitive is invoked:
10075+ // 1: via the DOM keyboard event thandler in squeak.js that intercepts cmd-c/cmd-v,
10076+ // reads/writes the system clipboard from/to display.clipboardString
10077+ // and then the interpreter calls the primitive
10078+ // 2: via the image code e.g. a menu copy/paste item, which calls the primitive
10079+ // and we try to read/write the system clipboard directly.
10080+ // To support this, squeak.js keeps running the interpreter for 100 ms within
10081+ // the DOM event 'mouseup' handler so the code below runs in the click-handler context,
10082+ // (otherwise the browser would block access to the clipboard)
1007410083 if (argCount === 0) { // read from clipboard
10075- if (typeof(this.display.clipboardString) !== 'string') return false;
10076- this.vm.popNandPush(1, this.makeStString(this.display.clipboardString));
10084+ // Try to read from system clipboard, which is async if available.
10085+ // It will likely fail outside of an event handler.
10086+ var clipBoardPromise = null;
10087+ if (this.display.readFromSystemClipboard) clipBoardPromise = this.display.readFromSystemClipboard();
10088+ if (clipBoardPromise) {
10089+ var unfreeze = this.vm.freeze();
10090+ clipBoardPromise
10091+ .then(() => this.vm.popNandPush(1, this.makeStString(this.display.clipboardString)))
10092+ .catch(() => this.vm.popNandPush(1, this.vm.nilObj))
10093+ .finally(() => unfreeze());
10094+ } else {
10095+ if (typeof(this.display.clipboardString) !== 'string') return false;
10096+ this.vm.popNandPush(1, this.makeStString(this.display.clipboardString));
10097+ }
1007710098 } else if (argCount === 1) { // write to clipboard
1007810099 var stringObj = this.vm.top();
1007910100 if (stringObj.bytes) {
1008010101 this.display.clipboardString = stringObj.bytesAsString();
10081- this.display.clipboardStringChanged = true;
10102+ this.display.clipboardStringChanged = true; // means it should be written to system clipboard
10103+ if (this.display.writeToSystemClipboard) {
10104+ // no need to wait for the promise
10105+ this.display.writeToSystemClipboard();
10106+ }
1008210107 }
1008310108 this.vm.pop();
1008410109 }
5627056295 display.signalInputEvent();
5627156296 }
5627256297 display.idle = 0;
56273- if (what == 'mouseup') {
56274- if (display.runFor) display.runFor(100); // maybe we catch the fullscreen flag change
56275- } else {
56276- if (display.runNow) display.runNow(); // don't wait for timeout to run
56277- }
56298+ if (what === 'mouseup') display.runFor(100, what); // process copy/paste or fullscreen flag change
56299+ else display.runNow(what); // don't wait for timeout to run
5627856300 }
5627956301
5628056302 function recordWheelEvent(evt, display) {
5629756319 if (display.signalInputEvent)
5629856320 display.signalInputEvent();
5629956321 display.idle = 0;
56322+ if (display.runNow) display.runNow('wheel'); // don't wait for timeout to run
5630056323 }
5630156324
5630256325 // Squeak traditional keycodes are MacRoman
5634856371 display.keys.push(modifiersAndKey);
5634956372 }
5635056373 display.idle = 0;
56351- if (display.runNow) display.runNow(); // don't wait for timeout to run
56374+ if (display.runNow) display.runNow('keyboard' ); // don't wait for timeout to run
5635256375 }
5635356376
5635456377 function recordDragDropEvent(type, evt, canvas, display) {
5636556388 ]);
5636656389 if (display.signalInputEvent)
5636756390 display.signalInputEvent();
56391+ display.idle = 0;
56392+ if (display.runNow) display.runNow('drag-drop'); // don't wait for timeout to run
5636856393 }
5636956394
5637056395 function fakeCmdOrCtrlKey(key, timestamp, display) {
5640456429 eventQueue: null, // only used if image uses event primitives
5640556430 clipboardString: '',
5640656431 clipboardStringChanged: false,
56432+ handlingEvent: '', // set to 'mouse' or 'keyboard' while handling an event
5640756433 cursorCanvas: options.cursor !== false && document.getElementById("sqCursor") || document.createElement("canvas"),
5640856434 cursorOffsetX: 0,
5640956435 cursorOffsetY: 0,
5649056516 ctx.fillStyle = style.color || "#F90";
5649156517 ctx.fillRect(x, y, w * value, h);
5649256518 };
56493- display.executeClipboardPaste = function(text, timestamp) {
56519+ display.executeClipboardPasteKey = function(text, timestamp) {
5649456520 if (!display.vm) return true;
5649556521 try {
5649656522 display.clipboardString = text;
5650056526 console.error("paste error " + err);
5650156527 }
5650256528 };
56503- display.executeClipboardCopy = function(key, timestamp) {
56529+ display.executeClipboardCopyKey = function(key, timestamp) {
5650456530 if (!display.vm) return true;
5650556531 // simulate copy event for Squeak so it places its text in clipboard
5650656532 display.clipboardStringChanged = false;
@@ -56906,18 +56932,18 @@
5690656932 switch (evt.key) {
5690756933 case "c":
5690856934 case "x":
56909- if (!navigator.clipboard?.writeText ) return; // fire document.oncopy/oncut
56910- var text = display.executeClipboardCopy (evt.key, evt.timeStamp);
56935+ if (!navigator.clipboard) return; // fire document.oncopy/oncut
56936+ var text = display.executeClipboardCopyKey (evt.key, evt.timeStamp);
5691156937 if (typeof text === 'string') {
5691256938 navigator.clipboard.writeText(text)
5691356939 .catch(function(err) { console.error("display: copy error " + err.message); });
5691456940 }
5691556941 return evt.preventDefault();
5691656942 case "v":
56917- if (!navigator.clipboard?.readText ) return; // fire document.onpaste
56943+ if (!navigator.clipboard) return; // fire document.onpaste
5691856944 navigator.clipboard.readText()
5691956945 .then(function(text) {
56920- display.executeClipboardPaste (text, evt.timeStamp);
56946+ display.executeClipboardPasteKey (text, evt.timeStamp);
5692156947 })
5692256948 .catch(function(err) { console.error("display: paste error " + err.message); });
5692356949 return evt.preventDefault();
@@ -56935,10 +56961,25 @@
5693556961 if (!display.vm) return true;
5693656962 recordModifiers(evt, display);
5693756963 };
56938- // copy/paste old-style
56939- if (!navigator.clipboard?.writeText) {
56964+ // more copy/paste
56965+ if (navigator.clipboard) {
56966+ // new-style copy/paste (all modern browsers)
56967+ display.readFromSystemClipboard = () => navigator.clipboard.readText()
56968+ .then(text => display.clipboardString = text)
56969+ .catch(err => {
56970+ if (!display.handlingEvent) console.warn("reading from clipboard outside event handler");
56971+ console.error("readFromSystemClipboard" + err.message);
56972+ });
56973+ display.writeToSystemClipboard = () => navigator.clipboard.writeText(display.clipboardString)
56974+ .then(() => display.clipboardStringChanged = false)
56975+ .catch(err => {
56976+ if (!display.handlingEvent) console.warn("writing to clipboard outside event handler");
56977+ console.error("writeToSystemClipboard" + err.message);
56978+ });
56979+ } else {
56980+ // old-style copy/paste
5694056981 document.oncopy = function(evt, key) {
56941- var text = display.executeClipboardCopy (key, evt.timeStamp);
56982+ var text = display.executeClipboardCopyKey (key, evt.timeStamp);
5694256983 if (typeof text === 'string') {
5694356984 evt.clipboardData.setData("Text", text);
5694456985 }
5694856989 if (!display.vm) return true;
5694956990 document.oncopy(evt, 'x');
5695056991 };
56951- }
56952- if (!navigator.clipboard?.readText) {
5695356992 document.onpaste = function(evt) {
5695456993 var text = evt.clipboardData.getData('Text');
56955- display.executeClipboardPaste (text, evt.timeStamp);
56994+ display.executeClipboardPasteKey (text, evt.timeStamp);
5695656995 evt.preventDefault();
5695756996 };
5695856997 }
@@ -57155,15 +57194,17 @@
5715557194 alert(error);
5715657195 }
5715757196 }
57158- display.runNow = function() {
57197+ display.runNow = function(event ) {
5715957198 window.clearTimeout(loop);
57199+ display.handlingEvent = event;
5716057200 run();
57201+ display.handlingEvent = '';
5716157202 };
57162- display.runFor = function(milliseconds) {
57203+ display.runFor = function(milliseconds, event ) {
5716357204 var stoptime = Date.now() + milliseconds;
5716457205 do {
5716557206 if (display.quitFlag) return;
57166- display.runNow();
57207+ display.runNow(event );
5716757208 } while (Date.now() < stoptime);
5716857209 };
5716957210 if (options.onStart) options.onStart(vm, display, options);
0 commit comments