Skip to content
Closed
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
19 changes: 10 additions & 9 deletions web/fizzy.js
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,16 @@ export class Fizzy {
focusQuietly(hidden);
}

// Forward keyboard edits to the fragment before the caret as typing to the worker.
// Declare before worker.onmessage, which may deliver textInput immediately.
const inputFilter = new TextInput(hidden, {
text: (text) => post({ type: "text", text }),
backspace: () => {
post({ type: "key", kind: 5, key: "Backspace", repeat: 0, mods: 0 });
post({ type: "key", kind: 6, key: "Backspace", repeat: 0, mods: 0 });
},
});

let boundKeys = new Set();
const offscreen = canvas.transferControlToOffscreen();
// The init waits for the early compile (`wasmModule`), which by now has usually finished.
Expand Down Expand Up @@ -1066,15 +1076,6 @@ export class Fizzy {
window.matchMedia(q).addEventListener("change", () => post({ type: "prefs", prefs: prefs() }));
}

// What the keyboard does to the fragment before the caret reaches the worker as typing.
const inputFilter = new TextInput(hidden, {
text: (text) => post({ type: "text", text }),
backspace: () => {
post({ type: "key", kind: 5, key: "Backspace", repeat: 0, mods: 0 });
post({ type: "key", kind: 6, key: "Backspace", repeat: 0, mods: 0 });
},
});

worker.onerror = (err) => {
console.error(err);
onError(`Failed to load the editor (${err.message || "worker error"}).`);
Expand Down
21 changes: 21 additions & 0 deletions web/tests/embed.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -143,18 +143,25 @@ console.log("embed.test: ok");
// Deliver text input as soon as the handler is installed, before setup completes.
let earlyTextInputs = 0;
let filterClears = 0;
const filteredKeys = [];
let workerInstance;
const { TextInput } = await import(pathToFileURL(path.join(dir, "web.js")).href);
TextInput.prototype.clear = () => { filterClears++; };
// This spy checks routing; text-input.test.js exercises the real filter and Backspace.
TextInput.prototype.keydown = (ev) => { filteredKeys.push(ev.key); };
Canvas.prototype.transferControlToOffscreen = function () { return {}; };
globalThis.OffscreenCanvas = class {};
globalThis.ResizeObserver = class { observe() {} };
const workerMessages = [];
globalThis.Worker = class {
constructor() { workerInstance = this; }
set onmessage(handler) {
this.handler = handler;
handler({ data: { type: "textInput", rect: [10, 20, 80, 30] } });
earlyTextInputs++;
}
postMessage(message) {
workerMessages.push(message);
if (message.type === "init") this.handler({ data: { type: "ready" } });
}
};
Expand All @@ -168,4 +175,18 @@ assert.equal(filterClears, 1, "entering text input clears the initialized filter
assert.equal(document.body.children[0].style.left, "10px", "early text input positions the hidden input");
workerCanvas.dispatch("pointerdown", { pointerId: 1, pointerType: "mouse", buttons: 1, clientX: 0, clientY: 0, timeStamp: 0 });
assert.equal(filterClears, 2, "pointer input still clears the filter");
const hidden = document.body.children[0];
assert.equal(key(hidden).defaultPrevented, false, "the worker hidden input is recognized without asking main-thread wasm");
for (const pressedKey of ["a", "Backspace"]) {
hidden.dispatch("keydown", { key: pressedKey, preventDefault() {} });
hidden.dispatch("keyup", { key: pressedKey });
}
assert.deepEqual(filteredKeys, ["a", "Backspace"], "typing and backspace are routed to the filter spy after early text input");
assert.deepEqual(workerMessages.filter((m) => m.type === "key").map((m) => [m.kind, m.key]), [
[5, "a"], [6, "a"], [5, "Backspace"], [6, "Backspace"],
], "typing and backspace still reach the worker");
workerInstance.handler({ data: { type: "textInput", rect: [20, 30, 80, 30] } });
assert.equal(filterClears, 2, "moving the text input preserves its fragment");
workerInstance.handler({ data: { type: "textInput", rect: [] } });
assert.equal(filterClears, 3, "leaving text input clears the filter");
console.log("embed.test: worker startup and early text input ok");
Loading