wasm improvements
This commit is contained in:
parent
42a479572d
commit
6aa0338c0f
9 changed files with 603 additions and 70 deletions
|
|
@ -109,10 +109,60 @@ function setStyle(cookie, stylePtr, styleLen) {
|
|||
const el = __jsmemory.get(cookie);
|
||||
if (el) el.style.cssText = __readUtf8(stylePtr, styleLen);
|
||||
}
|
||||
// NOTE: setProperty is a CSS custom-property setter (el.style.setProperty),
|
||||
// NOT setAttribute. The name is historical and misleading; the attribute
|
||||
// functions below are the ones you want for href/src/disabled/aria-*.
|
||||
function setProperty(cookie, propPtr, propLen, valPtr, valLen) {
|
||||
const el = __jsmemory.get(cookie);
|
||||
if (el) el.style.setProperty(__readUtf8(propPtr, propLen), __readUtf8(valPtr, valLen));
|
||||
}
|
||||
// Real attribute access. Without these the only way to change an href, src,
|
||||
// disabled or aria-* was to re-render the parent's innerHTML, which destroys
|
||||
// every descendant (and their event listeners) to change one string.
|
||||
function setAttribute(cookie, namePtr, nameLen, valPtr, valLen) {
|
||||
const el = __jsmemory.get(cookie);
|
||||
if (el) el.setAttribute(__readUtf8(namePtr, nameLen), __readUtf8(valPtr, valLen));
|
||||
}
|
||||
function removeAttribute(cookie, namePtr, nameLen) {
|
||||
const el = __jsmemory.get(cookie);
|
||||
if (el) el.removeAttribute(__readUtf8(namePtr, nameLen));
|
||||
}
|
||||
// Returns 0 for "absent" so the caller can tell it apart from an attribute
|
||||
// that is present with an empty value (`disabled=""`, `alt=""`) — which
|
||||
// matters because for boolean attributes presence alone is the signal.
|
||||
function getAttribute(cookie, namePtr, nameLen) {
|
||||
const el = __jsmemory.get(cookie);
|
||||
if (!el) return 0;
|
||||
const v = el.getAttribute(__readUtf8(namePtr, nameLen));
|
||||
if (v === null) return 0;
|
||||
return __writeUtf8(v);
|
||||
}
|
||||
function hasAttribute(cookie, namePtr, nameLen) {
|
||||
const el = __jsmemory.get(cookie);
|
||||
if (!el) return false;
|
||||
return el.hasAttribute(__readUtf8(namePtr, nameLen));
|
||||
}
|
||||
// Checkbox / radio state. el.value on a checkbox returns "on" regardless of
|
||||
// whether it is ticked, so GetValue cannot express this.
|
||||
function getChecked(cookie) {
|
||||
const el = __jsmemory.get(cookie);
|
||||
return !!(el && el.checked);
|
||||
}
|
||||
function setChecked(cookie, checked) {
|
||||
const el = __jsmemory.get(cookie);
|
||||
if (el) el.checked = !!checked;
|
||||
}
|
||||
// Move keyboard focus. AddFocusListener could observe focus but nothing
|
||||
// could set it, which makes an error summary ("jump to the first invalid
|
||||
// field") impossible to implement accessibly.
|
||||
function focusElement(cookie) {
|
||||
const el = __jsmemory.get(cookie);
|
||||
if (el && el.focus) el.focus();
|
||||
}
|
||||
function blurElement(cookie) {
|
||||
const el = __jsmemory.get(cookie);
|
||||
if (el && el.blur) el.blur();
|
||||
}
|
||||
function addClass(cookie, namePtr, nameLen) {
|
||||
const el = __jsmemory.get(cookie);
|
||||
if (el) el.classList.add(__readUtf8(namePtr, nameLen));
|
||||
|
|
@ -161,12 +211,25 @@ function __dpr() {
|
|||
return window.crafter_dpr || window.devicePixelRatio || 1;
|
||||
}
|
||||
|
||||
// `preventDefault` is opt-in per listener (the C++ Add*Listener overloads
|
||||
// take it as a trailing argument). Two reasons it has to exist here rather
|
||||
// than being something C++ can do after the fact:
|
||||
// * the wasm callback runs synchronously inside the dispatch, but the
|
||||
// decision has to be made before the browser acts on the event, and
|
||||
// there is no handle to the event object on the C++ side;
|
||||
// * intercepting a click on a real <a href> — the whole basis of
|
||||
// client-side routing over crawlable links — is impossible without it.
|
||||
// Cancelling happens BEFORE the wasm call so a trap or exception inside the
|
||||
// handler can't leave the default action to fire anyway.
|
||||
// `{ passive: false }` is required or the browser silently ignores
|
||||
// preventDefault on wheel (and warns); harmless on the other kinds.
|
||||
function __makeMouseListenerPair(kind, eventName, exportName) {
|
||||
return {
|
||||
add(cookie, id) {
|
||||
add(cookie, id, preventDefault) {
|
||||
const el = __jsmemory.get(cookie);
|
||||
if (!el) return;
|
||||
const handler = (event) => {
|
||||
if (preventDefault) event.preventDefault();
|
||||
const s = __dpr();
|
||||
__wasm()[exportName](id,
|
||||
event.clientX * s, event.clientY * s,
|
||||
|
|
@ -175,7 +238,7 @@ function __makeMouseListenerPair(kind, eventName, exportName) {
|
|||
event.altKey, event.ctrlKey, event.shiftKey, event.metaKey);
|
||||
};
|
||||
__listenerHandlers.set(`${cookie}-${id}-${kind}`, handler);
|
||||
el.addEventListener(eventName, handler);
|
||||
el.addEventListener(eventName, handler, preventDefault ? { passive: false } : undefined);
|
||||
},
|
||||
remove(cookie, id) {
|
||||
const el = __jsmemory.get(cookie);
|
||||
|
|
@ -188,17 +251,18 @@ function __makeMouseListenerPair(kind, eventName, exportName) {
|
|||
}
|
||||
function __makeKeyListenerPair(kind, eventName, exportName) {
|
||||
return {
|
||||
add(cookie, id) {
|
||||
add(cookie, id, preventDefault) {
|
||||
const el = __jsmemory.get(cookie);
|
||||
if (!el) return;
|
||||
const handler = (event) => {
|
||||
if (preventDefault) event.preventDefault();
|
||||
const keyPtr = __writeUtf8(event.key || "");
|
||||
__wasm()[exportName](id, keyPtr, event.keyCode,
|
||||
event.altKey, event.ctrlKey, event.shiftKey, event.metaKey);
|
||||
__wasm().WasmFree(keyPtr);
|
||||
};
|
||||
__listenerHandlers.set(`${cookie}-${id}-${kind}`, handler);
|
||||
el.addEventListener(eventName, handler);
|
||||
el.addEventListener(eventName, handler, preventDefault ? { passive: false } : undefined);
|
||||
},
|
||||
remove(cookie, id) {
|
||||
const el = __jsmemory.get(cookie);
|
||||
|
|
@ -339,9 +403,10 @@ const __scrollPair = {
|
|||
}
|
||||
};
|
||||
const __wheelPair = {
|
||||
add(cookie, id) {
|
||||
add(cookie, id, preventDefault) {
|
||||
const el = __jsmemory.get(cookie); if (!el) return;
|
||||
const handler = (event) => {
|
||||
if (preventDefault) event.preventDefault();
|
||||
const s = __dpr();
|
||||
__wasm().ExecuteWheelHandler(id,
|
||||
event.deltaX, event.deltaY, event.deltaZ, event.deltaMode,
|
||||
|
|
@ -350,7 +415,10 @@ const __wheelPair = {
|
|||
event.altKey, event.ctrlKey, event.shiftKey, event.metaKey);
|
||||
};
|
||||
__listenerHandlers.set(`${cookie}-${id}-wheel`, handler);
|
||||
el.addEventListener("wheel", handler);
|
||||
// passive:false is mandatory here — Chrome treats wheel as passive by
|
||||
// default and would otherwise drop the preventDefault with a console
|
||||
// warning rather than an error.
|
||||
el.addEventListener("wheel", handler, preventDefault ? { passive: false } : undefined);
|
||||
},
|
||||
remove(cookie, id) {
|
||||
const el = __jsmemory.get(cookie);
|
||||
|
|
@ -619,9 +687,45 @@ function removePopStateListener(id) {
|
|||
if (h) window.removeEventListener("popstate", h);
|
||||
__listenerHandlers.delete(`popstate-${id}`);
|
||||
}
|
||||
// replaceState mirrors pushState but rewrites the current history entry
|
||||
// instead of adding one. Needed for anything that changes the URL without
|
||||
// being a distinct "back" destination — filter/sort state, canonicalising a
|
||||
// sloppy incoming URL, or recording scroll position on the entry you are
|
||||
// about to leave.
|
||||
function replaceState(dataPtr, dataLen, titlePtr, titleLen, urlPtr, urlLen) {
|
||||
const dataStr = __readUtf8(dataPtr, dataLen);
|
||||
const titleStr = __readUtf8(titlePtr, titleLen);
|
||||
const urlStr = __readUtf8(urlPtr, urlLen);
|
||||
let parsed;
|
||||
try { parsed = JSON.parse(dataStr); } catch { parsed = null; }
|
||||
window.history.replaceState(parsed, titleStr, urlStr);
|
||||
}
|
||||
function getPathName() {
|
||||
return __writeUtf8(window.location.pathname);
|
||||
}
|
||||
// location.search / .hash, including their leading "?" / "#" so an empty
|
||||
// query and "?" are distinguishable, and so the result can be concatenated
|
||||
// straight back onto a path. pathname alone left query strings completely
|
||||
// unreachable from wasm, which made ?page= / ?sort= impossible to read.
|
||||
function getSearch() {
|
||||
return __writeUtf8(window.location.search);
|
||||
}
|
||||
function getHash() {
|
||||
return __writeUtf8(window.location.hash);
|
||||
}
|
||||
function getHref() {
|
||||
return __writeUtf8(window.location.href);
|
||||
}
|
||||
// Full-page navigation, including cross-origin. pushState deliberately
|
||||
// cannot leave the origin, so this is the only way for a wasm app to hand
|
||||
// the user off to an external URL (an OAuth consent screen, a hosted
|
||||
// payment page). `replace` omits the current page from history so Back
|
||||
// doesn't return to a stale checkout.
|
||||
function navigate(urlPtr, urlLen, replace) {
|
||||
const url = __readUtf8(urlPtr, urlLen);
|
||||
if (replace) window.location.replace(url);
|
||||
else window.location.assign(url);
|
||||
}
|
||||
|
||||
// ─── Gamepad polling helper ───────────────────────────────────────────
|
||||
//
|
||||
|
|
@ -659,6 +763,8 @@ Object.assign(window.crafter_webbuild_env, {
|
|||
// DOM lookup / creation / mutation
|
||||
freeJs, getElementById, createElement, getBody,
|
||||
setInnerHTML, setStyle, setProperty,
|
||||
setAttribute, removeAttribute, getAttribute, hasAttribute,
|
||||
getChecked, setChecked, focusElement, blurElement,
|
||||
addClass, removeClass, toggleClass, hasClass,
|
||||
deleteElement, getValue, setValue,
|
||||
|
||||
|
|
@ -697,7 +803,8 @@ Object.assign(window.crafter_webbuild_env, {
|
|||
clipboardSetText, clipboardGetText,
|
||||
|
||||
// History
|
||||
pushState, addPopStateListener, removePopStateListener, getPathName,
|
||||
pushState, replaceState, addPopStateListener, removePopStateListener,
|
||||
getPathName, getSearch, getHash, getHref, navigate,
|
||||
|
||||
// Gamepad
|
||||
gamepadPollConnected, gamepadPollDisconnected,
|
||||
|
|
|
|||
Loading…
Reference in a new issue