@daugasauron/dolly-host-html 0.0.0-stage → 0.1.5-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/abi.mjs +10 -0
- package/html.mjs +266 -0
- package/module.json +14 -0
- package/package.json +12 -4
package/README.md
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @daugasauron/dolly-host-html
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Dolly's `html@0` host module: show the user an HTML page or form; the user answers or closes it. Give the URL of its
|
|
4
|
+
`module.json` to `boot()`'s `modules`; @daugasauron/dolly's README says how.
|
package/abi.mjs
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Generated from host/html/dolly-html-0.wat, host/html/html.h, include/dolly/process.h.
|
|
2
|
+
export const DOLLY_HTML_OPEN = 1;
|
|
3
|
+
export const DOLLY_HTML_ANSWER = 2;
|
|
4
|
+
export const DOLLY_HTML_CLOSE = 3;
|
|
5
|
+
export const DOLLY_HTML_DOCUMENT_MAX = 1048576;
|
|
6
|
+
export const DOLLY_HTML_FIELDS_MAX = 65536;
|
|
7
|
+
export const DOLLY_HTML_SHOW = 192;
|
|
8
|
+
export const DOLLY_HTML_WAIT = 193;
|
|
9
|
+
export const DOLLY_ERRNO = Object.freeze({ E2BIG: 1, EACCES: 2, EADDRINUSE: 3, EADDRNOTAVAIL: 4, EAFNOSUPPORT: 5, EAGAIN: 6, EALREADY: 7, EBADF: 8, EBADMSG: 9, EBUSY: 10, ECANCELED: 11, ECHILD: 12, ECONNABORTED: 13, ECONNREFUSED: 14, ECONNRESET: 15, EDEADLK: 16, EDESTADDRREQ: 17, EDOM: 18, EDQUOT: 19, EEXIST: 20, EFAULT: 21, EFBIG: 22, EHOSTUNREACH: 23, EIDRM: 24, EILSEQ: 25, EINPROGRESS: 26, EINTR: 27, EINVAL: 28, EIO: 29, EISCONN: 30, EISDIR: 31, ELOOP: 32, EMFILE: 33, EMLINK: 34, EMSGSIZE: 35, EMULTIHOP: 36, ENAMETOOLONG: 37, ENETDOWN: 38, ENETRESET: 39, ENETUNREACH: 40, ENFILE: 41, ENOBUFS: 42, ENODEV: 43, ENOENT: 44, ENOEXEC: 45, ENOLCK: 46, ENOLINK: 47, ENOMEM: 48, ENOMSG: 49, ENOPROTOOPT: 50, ENOSPC: 51, ENOSYS: 52, ENOTCONN: 53, ENOTDIR: 54, ENOTEMPTY: 55, ENOTRECOVERABLE: 56, ENOTSOCK: 57, ENOTTY: 59, ENXIO: 60, EOVERFLOW: 61, EOWNERDEAD: 62, EPERM: 63, EPIPE: 64, EPROTO: 65, EPROTONOSUPPORT: 66, EPROTOTYPE: 67, ERANGE: 68, EROFS: 69, ESPIPE: 70, ESRCH: 71, ESTALE: 72, ETIMEDOUT: 73, ETXTBSY: 74, EXDEV: 75, ENODATA: 116, ENOTSUP: 138 });
|
|
10
|
+
export const DOLLY_HTML_ABI_DIGEST = "7815d3e1e60cbfd02d2f2c4e81ebeb9a2f746ffae05172d147dfc6c3f7745a58";
|
package/html.mjs
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { DOLLY_ERRNO as E, DOLLY_HTML_OPEN, DOLLY_HTML_ANSWER, DOLLY_HTML_CLOSE, DOLLY_HTML_DOCUMENT_MAX,
|
|
2
|
+
DOLLY_HTML_FIELDS_MAX } from "./abi.mjs";
|
|
3
|
+
export { DOLLY_HTML_ABI_DIGEST as digest } from "./abi.mjs";
|
|
4
|
+
|
|
5
|
+
// After the user closes a popup, the next is refused for this long, so the
|
|
6
|
+
// user reaches the terminal (and Ctrl+C) before a program shows another.
|
|
7
|
+
const quietMilliseconds = 2000;
|
|
8
|
+
|
|
9
|
+
// The popup's look is the page's: an embedding sets these on boot()'s element
|
|
10
|
+
// (or above it) in its own style sheet; unset, they are Dolly's. The first
|
|
11
|
+
// three are the terminal's colours too (display@0). A font file,
|
|
12
|
+
// --dolly-html-font-file: url(…), is the font the first family names.
|
|
13
|
+
const themeProperties = Object.freeze({
|
|
14
|
+
"--dolly-background": "#262626",
|
|
15
|
+
"--dolly-color": "#e8e3d7",
|
|
16
|
+
"--dolly-accent": "#f2d45c",
|
|
17
|
+
"--dolly-html-font": "ui-monospace, monospace",
|
|
18
|
+
"--dolly-html-color-scheme": "dark",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// The popup's document: a program's styles are left out, so this is all of it.
|
|
22
|
+
const popupStyle = `
|
|
23
|
+
:root { color-scheme: var(--dolly-html-color-scheme); accent-color: var(--dolly-accent);
|
|
24
|
+
--line: color-mix(in srgb, var(--dolly-color) 35%, var(--dolly-background));
|
|
25
|
+
--panel: color-mix(in srgb, var(--dolly-color) 9%, var(--dolly-background));
|
|
26
|
+
color: var(--dolly-color); background: var(--dolly-background); font: 16px/1.5 var(--dolly-html-font); }
|
|
27
|
+
body { margin: 0; padding: 1.25rem 1.5rem; overflow-wrap: anywhere; }
|
|
28
|
+
body > :first-child { margin-top: 0; }
|
|
29
|
+
h1, h2, h3, h4, h5, h6 { margin: 1.4em 0 0.5em; color: var(--dolly-accent); font-size: 1rem; line-height: 1.3; }
|
|
30
|
+
h1 { font-size: 1.5rem; } h2 { font-size: 1.25rem; } h3 { font-size: 1.1rem; }
|
|
31
|
+
p, ul, ol, dl, pre, blockquote, table, fieldset, details, figure { margin: 0 0 1rem; }
|
|
32
|
+
ul, ol { padding-left: 1.6rem; } dt { font-weight: bold; } dd { margin-left: 1.6rem; }
|
|
33
|
+
table { border-collapse: collapse; } caption { padding-bottom: 0.4rem; text-align: left; }
|
|
34
|
+
th, td { padding: 0.3rem 0.7rem; border: 1px solid var(--line); text-align: left; vertical-align: top; }
|
|
35
|
+
th { background: var(--panel); }
|
|
36
|
+
pre, code, kbd, samp { font-family: inherit; } pre { padding: 0.7rem 0.9rem; overflow: auto; background: var(--panel); }
|
|
37
|
+
blockquote { margin-left: 0; padding-left: 1rem; border-left: 3px solid var(--line); }
|
|
38
|
+
hr { border: 0; border-top: 1px solid var(--line); }
|
|
39
|
+
mark { color: var(--dolly-background); background: var(--dolly-accent); }
|
|
40
|
+
summary { cursor: pointer; }
|
|
41
|
+
details { margin: 0; border-top: 1px solid var(--line); } details details { margin-left: 0.75rem; }
|
|
42
|
+
details > summary { padding: 0.75rem 0; color: var(--dolly-accent); }
|
|
43
|
+
/* A nav of buttons is a menu: a grid of buttons a finger can hit. */
|
|
44
|
+
nav { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(12rem, 100%), 1fr)); gap: 0.5rem; margin: 0 0 1rem; }
|
|
45
|
+
nav > button { min-height: 2.75rem; margin: 0; text-align: left; }
|
|
46
|
+
button small { display: block; opacity: 0.7; font-size: 0.85em; }
|
|
47
|
+
fieldset { padding: 0.75rem 1rem; border: 1px solid var(--line); } legend { padding: 0 0.4rem; }
|
|
48
|
+
label { display: inline-block; margin: 0.2rem 0; }
|
|
49
|
+
input, select, textarea, button { margin: 0.2rem 0; padding: 0.3rem 0.5rem; color: inherit; background: var(--panel);
|
|
50
|
+
border: 1px solid var(--line); border-radius: 0; font: inherit; }
|
|
51
|
+
input[type=checkbox], input[type=radio], input[type=range], input[type=color] { padding: 0; }
|
|
52
|
+
textarea { display: block; width: 100%; box-sizing: border-box; }
|
|
53
|
+
button, input[type=submit], input[type=reset] { padding: 0.35rem 1.1rem; cursor: pointer; }
|
|
54
|
+
button:not([type=reset]):not([type=button]), input[type=submit] { border-color: var(--dolly-accent); }
|
|
55
|
+
:focus-visible { outline: 2px solid var(--dolly-accent); outline-offset: 1px; }
|
|
56
|
+
:user-invalid { border-color: #e07a5f; }
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
// The page's one script in the popup's frame, given the document as a string
|
|
60
|
+
// and run before anything else there. It builds the popup from the document's
|
|
61
|
+
// text and the elements and attributes below, copied into new elements of its
|
|
62
|
+
// own: the document is never parsed in a page that could load or navigate,
|
|
63
|
+
// and nothing else of it exists in the popup. It reports a submitted form's
|
|
64
|
+
// fields and Escape to the page.
|
|
65
|
+
function popup({ source, theme, font, origin }) {
|
|
66
|
+
const shown = new Set(("h1 h2 h3 h4 h5 h6 p br hr div span section article header footer main aside nav " +
|
|
67
|
+
"blockquote pre code kbd samp var em strong b i u s small mark sub sup abbr cite q dfn time data del ins " +
|
|
68
|
+
"figure figcaption address ul ol li dl dt dd table caption colgroup col thead tbody tfoot tr th td " +
|
|
69
|
+
"details summary form fieldset legend label input button select option optgroup textarea output progress meter")
|
|
70
|
+
.split(" "));
|
|
71
|
+
// Shown without the element: an unknown element's text stays, these go with theirs.
|
|
72
|
+
const dropped = new Set("title script style template noscript iframe object embed video audio canvas datalist".split(" "));
|
|
73
|
+
const attributes = {
|
|
74
|
+
"*": ["id", "title", "lang", "dir"],
|
|
75
|
+
ol: ["start", "reversed", "type"], li: ["value"], col: ["span"], colgroup: ["span"],
|
|
76
|
+
th: ["colspan", "rowspan", "headers", "scope", "abbr"], td: ["colspan", "rowspan", "headers"],
|
|
77
|
+
time: ["datetime"], data: ["value"], del: ["datetime"], ins: ["datetime"], details: ["open"],
|
|
78
|
+
form: ["novalidate"], fieldset: ["name", "disabled"], label: ["for"], output: ["name", "for", "form"],
|
|
79
|
+
input: ["type", "name", "value", "checked", "disabled", "readonly", "required", "min", "max", "step",
|
|
80
|
+
"minlength", "maxlength", "placeholder", "size", "multiple", "inputmode", "form"],
|
|
81
|
+
button: ["type", "name", "value", "disabled", "form"], select: ["name", "multiple", "required", "disabled", "size", "form"],
|
|
82
|
+
option: ["value", "selected", "disabled", "label"], optgroup: ["label", "disabled"],
|
|
83
|
+
textarea: ["name", "rows", "cols", "placeholder", "required", "readonly", "disabled", "minlength", "maxlength", "wrap", "form"],
|
|
84
|
+
progress: ["value", "max"], meter: ["value", "min", "max", "low", "high", "optimum"],
|
|
85
|
+
};
|
|
86
|
+
// Controls of other types are left out: a password field would invite the
|
|
87
|
+
// browser to fill one in, a file field to choose a file, an image to load
|
|
88
|
+
// one, and a plain button does nothing without a script.
|
|
89
|
+
const types = { input: new Set(("text search email url tel number range date month week time datetime-local " +
|
|
90
|
+
"color checkbox radio hidden submit reset").split(" ")), button: new Set(["submit", "reset"]) };
|
|
91
|
+
const copy = (from, to) => {
|
|
92
|
+
for (const node of from.childNodes) {
|
|
93
|
+
if (node.nodeType === Node.TEXT_NODE) to.append(node.data);
|
|
94
|
+
if (node.nodeType !== Node.ELEMENT_NODE || node.namespaceURI !== "http://www.w3.org/1999/xhtml" ||
|
|
95
|
+
dropped.has(node.localName)) continue;
|
|
96
|
+
if (!shown.has(node.localName)) { copy(node, to); continue; }
|
|
97
|
+
const type = node.getAttribute("type")?.toLowerCase();
|
|
98
|
+
if (types[node.localName] && type !== undefined && !types[node.localName].has(type)) continue;
|
|
99
|
+
const element = document.createElement(node.localName);
|
|
100
|
+
for (const name of [...attributes["*"], ...attributes[node.localName] ?? []]) {
|
|
101
|
+
if (node.hasAttribute(name)) element.setAttribute(name, node.getAttribute(name));
|
|
102
|
+
}
|
|
103
|
+
copy(node, element);
|
|
104
|
+
to.append(element);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
document.currentScript.remove();
|
|
108
|
+
copy(new DOMParser().parseFromString(source, "text/html").body, document.body);
|
|
109
|
+
for (const [name, value] of Object.entries(theme)) document.documentElement.style.setProperty(name, value);
|
|
110
|
+
if (font) document.fonts.add(new FontFace(theme["--dolly-html-font"].split(",")[0].trim().replace(/^(["'])(.*)\1$/, "$2"),
|
|
111
|
+
Uint8Array.from(atob(font), character => character.charCodeAt(0))));
|
|
112
|
+
document.addEventListener("submit", event => {
|
|
113
|
+
event.preventDefault();
|
|
114
|
+
parent.postMessage({ fields: String(new URLSearchParams(new FormData(event.target, event.submitter))) }, origin);
|
|
115
|
+
});
|
|
116
|
+
addEventListener("keydown", event => { if (event.key === "Escape") parent.postMessage({ close: true }, origin); });
|
|
117
|
+
document.querySelector("input:not([type=hidden]), select, textarea, button")?.focus();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// The frame: an opaque origin (no allow-same-origin) that may run the page's
|
|
121
|
+
// script (its nonce) and submit forms to it, and fetches nothing, as its
|
|
122
|
+
// policy says. Without allow-popups, allow-top-navigation or allow-modals it
|
|
123
|
+
// opens nothing and navigates no other page.
|
|
124
|
+
function frameSource({ source, theme, font }) {
|
|
125
|
+
const nonce = crypto.randomUUID();
|
|
126
|
+
const policy = `default-src 'none'; script-src 'nonce-${nonce}'; style-src 'nonce-${nonce}'; form-action 'none'; base-uri 'none'`;
|
|
127
|
+
const literal = JSON.stringify({ source, theme, font, origin: location.origin }).replaceAll("<", "\\u003c");
|
|
128
|
+
return `<!doctype html><html><head><meta http-equiv="Content-Security-Policy" content="${policy}">` +
|
|
129
|
+
`<style nonce="${nonce}">${popupStyle}</style></head><body><script nonce="${nonce}">(${popup})(${literal})</script></body></html>`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const pageStyle = `
|
|
133
|
+
#html-popup { position: absolute; inset: 0; z-index: 8; display: flex; align-items: center; justify-content: center;
|
|
134
|
+
padding: 1rem; background: rgb(0 0 0 / 0.55); }
|
|
135
|
+
#html-popup > div { display: flex; flex-direction: column; width: min(56rem, 100%); height: 100%; max-height: 48rem;
|
|
136
|
+
background: var(--dolly-background, ${themeProperties["--dolly-background"]});
|
|
137
|
+
border: 1px solid var(--dolly-accent, ${themeProperties["--dolly-accent"]}); }
|
|
138
|
+
#html-popup button { align-self: flex-end; margin: 0.3rem; padding: 0.2rem 0.9rem;
|
|
139
|
+
color: var(--dolly-color, ${themeProperties["--dolly-color"]}); background: transparent;
|
|
140
|
+
border: 1px solid currentColor; font: 14px/1.5 var(--dolly-html-font, ${themeProperties["--dolly-html-font"]}); cursor: pointer; }
|
|
141
|
+
#html-popup iframe { flex: 1; width: 100%; border: 0; }
|
|
142
|
+
`;
|
|
143
|
+
|
|
144
|
+
// The embedding's font file, as base64, fetched once by the page: the popup fetches nothing.
|
|
145
|
+
const fontFiles = new Map();
|
|
146
|
+
function fontFile(style) {
|
|
147
|
+
const location = /^url\(\s*(["']?)(.+)\1\s*\)$/.exec(style.getPropertyValue("--dolly-html-font-file").trim())?.[2];
|
|
148
|
+
if (!location) return null;
|
|
149
|
+
const url = new URL(location, document.baseURI).href;
|
|
150
|
+
if (!fontFiles.has(url)) fontFiles.set(url, fetch(url).then(response => response.ok ? response.arrayBuffer() : null).then(buffer => {
|
|
151
|
+
if (!buffer) return null;
|
|
152
|
+
const bytes = new Uint8Array(buffer);
|
|
153
|
+
let text = "";
|
|
154
|
+
for (let offset = 0; offset < bytes.length; offset += 32768) text += String.fromCharCode(...bytes.subarray(offset, offset + 32768));
|
|
155
|
+
return btoa(text);
|
|
156
|
+
}).catch(() => null));
|
|
157
|
+
return fontFiles.get(url);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// The page shows one popup in boot()'s element, over the terminal, until the
|
|
161
|
+
// user submits a form of it or closes it (its Close button, or Escape), or
|
|
162
|
+
// the program withdraws it.
|
|
163
|
+
export function browser({ mount, keyboard, send }) {
|
|
164
|
+
const look = document.head.appendChild(Object.assign(document.createElement("style"), { textContent: pageStyle }));
|
|
165
|
+
let shown = null;
|
|
166
|
+
function remove() {
|
|
167
|
+
shown?.element?.remove();
|
|
168
|
+
shown = null;
|
|
169
|
+
keyboard?.focus({ preventScroll: true });
|
|
170
|
+
}
|
|
171
|
+
function answer(fields) {
|
|
172
|
+
send({ type: "html-answer", popup: shown.popup, fields });
|
|
173
|
+
remove();
|
|
174
|
+
}
|
|
175
|
+
function received(event) {
|
|
176
|
+
if (!shown?.frame || event.source !== shown.frame.contentWindow) return;
|
|
177
|
+
const { fields, close } = event.data ?? {};
|
|
178
|
+
if (close === true) answer(null);
|
|
179
|
+
else if (typeof fields === "string" && fields.length <= DOLLY_HTML_FIELDS_MAX) answer(fields);
|
|
180
|
+
}
|
|
181
|
+
addEventListener("message", received);
|
|
182
|
+
return {
|
|
183
|
+
messages: {
|
|
184
|
+
async "html-show"({ popup, document: source }) {
|
|
185
|
+
if (!Number.isSafeInteger(popup) || typeof source !== "string") throw new Error("Dolly supplied an invalid popup");
|
|
186
|
+
if (shown) remove();
|
|
187
|
+
const style = getComputedStyle(mount);
|
|
188
|
+
const theme = Object.fromEntries(Object.entries(themeProperties)
|
|
189
|
+
.map(([name, fallback]) => [name, style.getPropertyValue(name).trim() || fallback]));
|
|
190
|
+
// The program may withdraw the popup while the font is fetched.
|
|
191
|
+
shown = { popup };
|
|
192
|
+
const font = await fontFile(style);
|
|
193
|
+
if (shown?.popup !== popup) return;
|
|
194
|
+
const element = document.createElement("div"), box = document.createElement("div");
|
|
195
|
+
const close = Object.assign(document.createElement("button"), { type: "button", textContent: "Close" });
|
|
196
|
+
const frame = Object.assign(document.createElement("iframe"), { title: "A page a program in Dolly shows" });
|
|
197
|
+
element.id = "html-popup";
|
|
198
|
+
element.setAttribute("role", "dialog");
|
|
199
|
+
element.setAttribute("aria-label", "A page a program in Dolly shows");
|
|
200
|
+
frame.setAttribute("sandbox", "allow-scripts allow-forms");
|
|
201
|
+
frame.srcdoc = frameSource({ source, theme, font });
|
|
202
|
+
close.addEventListener("click", () => answer(null));
|
|
203
|
+
element.addEventListener("keydown", event => { if (event.key === "Escape") answer(null); });
|
|
204
|
+
box.append(close, frame);
|
|
205
|
+
element.append(box);
|
|
206
|
+
shown = { popup, element, frame };
|
|
207
|
+
mount.append(element);
|
|
208
|
+
frame.focus();
|
|
209
|
+
},
|
|
210
|
+
"html-close"({ popup }) { if (shown?.popup === popup) remove(); },
|
|
211
|
+
},
|
|
212
|
+
// The popup's keys are its own; Ctrl+C there still interrupts the program.
|
|
213
|
+
claimsKey: event => shown?.element?.contains(event.target) ? "interrupt" : false,
|
|
214
|
+
dispose() { shown?.element?.remove(); shown = null; look.remove(); removeEventListener("message", received); },
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// The Worker holds the popup shown and what the user did with it, which the
|
|
219
|
+
// kernel's ANSWER takes once.
|
|
220
|
+
export function worker({ send, get }) {
|
|
221
|
+
const decode = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
222
|
+
const encode = new TextEncoder();
|
|
223
|
+
let shown = null, popups = 0, quietUntil = 0;
|
|
224
|
+
return {
|
|
225
|
+
messages: {
|
|
226
|
+
"html-answer"({ popup, fields }) {
|
|
227
|
+
if (shown?.popup !== popup || "fields" in shown || fields !== null && typeof fields !== "string") return;
|
|
228
|
+
shown.fields = fields;
|
|
229
|
+
if (fields === null) quietUntil = performance.now() + quietMilliseconds;
|
|
230
|
+
get("runtime").serviceDeferred();
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
bindings: { "env.dolly_html_dispatch": (operation, address, length) => {
|
|
234
|
+
const memory = get("runtime").memory.buffer;
|
|
235
|
+
const [start, size] = [address, length].map(Number);
|
|
236
|
+
if (![start, size].every(Number.isSafeInteger) || start < 0 || size < 0 ||
|
|
237
|
+
start > memory.byteLength - size) return -E.EINVAL;
|
|
238
|
+
if (operation === DOLLY_HTML_OPEN) {
|
|
239
|
+
if (size < 1 || size > DOLLY_HTML_DOCUMENT_MAX) return -E.EINVAL;
|
|
240
|
+
if (shown) return -E.EBUSY;
|
|
241
|
+
if (performance.now() < quietUntil) return -E.ECANCELED;
|
|
242
|
+
let document;
|
|
243
|
+
try { document = decode.decode(new Uint8Array(memory, start, size).slice()); }
|
|
244
|
+
catch { return -E.EILSEQ; }
|
|
245
|
+
shown = { popup: ++popups };
|
|
246
|
+
send({ type: "html-show", popup: shown.popup, document });
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
249
|
+
if (!shown) return -E.EBADF;
|
|
250
|
+
if (operation === DOLLY_HTML_ANSWER) {
|
|
251
|
+
if (!("fields" in shown)) return -E.EAGAIN;
|
|
252
|
+
const { fields } = shown;
|
|
253
|
+
shown = null;
|
|
254
|
+
if (fields === null) return -E.ECANCELED;
|
|
255
|
+
const bytes = encode.encode(fields);
|
|
256
|
+
if (bytes.length > Math.min(size, DOLLY_HTML_FIELDS_MAX)) return -E.EMSGSIZE;
|
|
257
|
+
new Uint8Array(memory, start, bytes.length).set(bytes);
|
|
258
|
+
return bytes.length;
|
|
259
|
+
}
|
|
260
|
+
if (operation !== DOLLY_HTML_CLOSE || size !== 0) return -E.EINVAL;
|
|
261
|
+
send({ type: "html-close", popup: shown.popup });
|
|
262
|
+
shown = null;
|
|
263
|
+
return 0;
|
|
264
|
+
} },
|
|
265
|
+
};
|
|
266
|
+
}
|
package/module.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "html",
|
|
3
|
+
"version": 0,
|
|
4
|
+
"gives": "show the user an HTML page or form; the user answers or closes it",
|
|
5
|
+
"dependencies": ["runtime@0"],
|
|
6
|
+
"imports": ["env.dolly_html_dispatch"],
|
|
7
|
+
"host": "html.mjs",
|
|
8
|
+
"contracts": ["dolly-html-0.wat"],
|
|
9
|
+
"process": [],
|
|
10
|
+
"headers": ["html.h", "html-abi.h"],
|
|
11
|
+
"kernel": ["kernel.c"],
|
|
12
|
+
"client": ["client.c"],
|
|
13
|
+
"package": "html"
|
|
14
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
{
|
|
2
|
+
"version": "0.1.5-rc.0",
|
|
3
|
+
"type": "module",
|
|
2
4
|
"name": "@daugasauron/dolly-host-html",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
}
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@daugasauron/dolly": "0.1.5-rc.0"
|
|
8
|
+
},
|
|
9
|
+
"description": "Dolly's html@0 host module: show the user an HTML page or form; the user answers or closes it.",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/daugasauron/dolly.git"
|
|
13
|
+
}
|
|
14
|
+
}
|