SMJS: move comments to match spidermonkeyFunctionSpec[]
[elinks/elinks-j605.git] / src / ecmascript / spidermonkey / form.c
blobd448f0169b44b1cb51bce9bfcb3ef76e5a9d3fb0
1 /* The SpiderMonkey window object implementation. */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
11 #include "elinks.h"
13 #include "ecmascript/spidermonkey/util.h"
15 #include "bfu/dialog.h"
16 #include "cache/cache.h"
17 #include "cookies/cookies.h"
18 #include "dialogs/menu.h"
19 #include "dialogs/status.h"
20 #include "document/html/frames.h"
21 #include "document/document.h"
22 #include "document/forms.h"
23 #include "document/view.h"
24 #include "ecmascript/ecmascript.h"
25 #include "ecmascript/spidermonkey.h"
26 #include "ecmascript/spidermonkey/document.h"
27 #include "ecmascript/spidermonkey/form.h"
28 #include "ecmascript/spidermonkey/window.h"
29 #include "intl/gettext/libintl.h"
30 #include "main/select.h"
31 #include "osdep/newwin.h"
32 #include "osdep/sysname.h"
33 #include "protocol/http/http.h"
34 #include "protocol/uri.h"
35 #include "session/history.h"
36 #include "session/location.h"
37 #include "session/session.h"
38 #include "session/task.h"
39 #include "terminal/tab.h"
40 #include "terminal/terminal.h"
41 #include "util/conv.h"
42 #include "util/memory.h"
43 #include "util/string.h"
44 #include "viewer/text/draw.h"
45 #include "viewer/text/form.h"
46 #include "viewer/text/link.h"
47 #include "viewer/text/vs.h"
50 static const JSClass form_class; /* defined below */
53 /* Accordingly to the JS specs, each input type should own object. That'd be a
54 * huge PITA though, however DOM comes to the rescue and defines just a single
55 * HTMLInputElement. The difference could be spotted only by some clever tricky
56 * JS code, but I hope it doesn't matter anywhere. --pasky */
58 static JSBool input_get_property(JSContext *ctx, JSObject *obj, jsid id, jsval *vp);
59 static JSBool input_set_property(JSContext *ctx, JSObject *obj, jsid id, JSBool strict, jsval *vp);
60 static void input_finalize(JSContext *ctx, JSObject *obj);
62 /* Each @input_class object must have a @form_class parent. */
63 static const JSClass input_class = {
64 "input", /* here, we unleash ourselves */
65 JSCLASS_HAS_PRIVATE, /* struct form_state *, or NULL if detached */
66 JS_PropertyStub, JS_PropertyStub,
67 input_get_property, input_set_property,
68 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, input_finalize
71 /* Tinyids of properties. Use negative values to distinguish these
72 * from array indexes (even though this object has no array elements).
73 * ECMAScript code should not use these directly as in input[-1];
74 * future versions of ELinks may change the numbers. */
75 enum input_prop {
76 JSP_INPUT_ACCESSKEY = -1,
77 JSP_INPUT_ALT = -2,
78 JSP_INPUT_CHECKED = -3,
79 JSP_INPUT_DEFAULT_CHECKED = -4,
80 JSP_INPUT_DEFAULT_VALUE = -5,
81 JSP_INPUT_DISABLED = -6,
82 JSP_INPUT_FORM = -7,
83 JSP_INPUT_MAX_LENGTH = -8,
84 JSP_INPUT_NAME = -9,
85 JSP_INPUT_READONLY = -10,
86 JSP_INPUT_SELECTED_INDEX = -11,
87 JSP_INPUT_SIZE = -12,
88 JSP_INPUT_SRC = -13,
89 JSP_INPUT_TABINDEX = -14,
90 JSP_INPUT_TYPE = -15,
91 JSP_INPUT_VALUE = -16,
94 /* XXX: Some of those are marked readonly just because we can't change them
95 * safely now. Changing default* values would affect all open instances of the
96 * document, leading to a potential security risk. Changing size and type would
97 * require re-rendering the document (TODO), tabindex would require renumbering
98 * of all links and whatnot. --pasky */
99 static const JSPropertySpec input_props[] = {
100 { "accessKey", JSP_INPUT_ACCESSKEY, JSPROP_ENUMERATE },
101 { "alt", JSP_INPUT_ALT, JSPROP_ENUMERATE },
102 { "checked", JSP_INPUT_CHECKED, JSPROP_ENUMERATE },
103 { "defaultChecked",JSP_INPUT_DEFAULT_CHECKED,JSPROP_ENUMERATE },
104 { "defaultValue",JSP_INPUT_DEFAULT_VALUE,JSPROP_ENUMERATE },
105 { "disabled", JSP_INPUT_DISABLED, JSPROP_ENUMERATE },
106 { "form", JSP_INPUT_FORM, JSPROP_ENUMERATE | JSPROP_READONLY },
107 { "maxLength", JSP_INPUT_MAX_LENGTH, JSPROP_ENUMERATE },
108 { "name", JSP_INPUT_NAME, JSPROP_ENUMERATE },
109 { "readonly", JSP_INPUT_READONLY, JSPROP_ENUMERATE },
110 { "selectedIndex",JSP_INPUT_SELECTED_INDEX,JSPROP_ENUMERATE },
111 { "size", JSP_INPUT_SIZE, JSPROP_ENUMERATE | JSPROP_READONLY },
112 { "src", JSP_INPUT_SRC, JSPROP_ENUMERATE },
113 { "tabindex", JSP_INPUT_TABINDEX, JSPROP_ENUMERATE | JSPROP_READONLY },
114 { "type", JSP_INPUT_TYPE, JSPROP_ENUMERATE | JSPROP_READONLY },
115 { "value", JSP_INPUT_VALUE, JSPROP_ENUMERATE },
116 { NULL }
119 static JSBool input_blur(JSContext *ctx, uintN argc, jsval *rval);
120 static JSBool input_click(JSContext *ctx, uintN argc, jsval *rval);
121 static JSBool input_focus(JSContext *ctx, uintN argc, jsval *rval);
122 static JSBool input_select(JSContext *ctx, uintN argc, jsval *rval);
124 static const spidermonkeyFunctionSpec input_funcs[] = {
125 { "blur", input_blur, 0 },
126 { "click", input_click, 0 },
127 { "focus", input_focus, 0 },
128 { "select", input_select, 0 },
129 { NULL }
132 static JSString *unicode_to_jsstring(JSContext *ctx, unicode_val_T u);
133 static unicode_val_T jsval_to_accesskey(JSContext *ctx, jsval *vp);
136 static struct form_state *
137 input_get_form_state(JSContext *ctx, JSObject *jsinput)
139 struct form_state *fs = JS_GetInstancePrivate(ctx, jsinput,
140 (JSClass *) &input_class,
141 NULL);
143 if (!fs) return NULL; /* detached */
145 assert(fs->ecmascript_obj == jsinput);
146 if_assert_failed return NULL;
148 return fs;
151 /* @input_class.getProperty */
152 static JSBool
153 input_get_property(JSContext *ctx, JSObject *obj, jsid id, jsval *vp)
155 JSObject *parent_form; /* instance of @form_class */
156 JSObject *parent_doc; /* instance of @document_class */
157 JSObject *parent_win; /* instance of @window_class */
158 struct view_state *vs;
159 struct document_view *doc_view;
160 struct document *document;
161 struct form_state *fs;
162 struct form_control *fc;
163 int linknum;
164 struct link *link = NULL;
166 /* This can be called if @obj if not itself an instance of the
167 * appropriate class but has one in its prototype chain. Fail
168 * such calls. */
169 if (!JS_InstanceOf(ctx, obj, (JSClass *) &input_class, NULL))
170 return JS_FALSE;
171 parent_form = JS_GetParent(ctx, obj);
172 assert(JS_InstanceOf(ctx, parent_form, (JSClass *) &form_class, NULL));
173 if_assert_failed return JS_FALSE;
174 parent_doc = JS_GetParent(ctx, parent_form);
175 assert(JS_InstanceOf(ctx, parent_doc, (JSClass *) &document_class, NULL));
176 if_assert_failed return JS_FALSE;
177 parent_win = JS_GetParent(ctx, parent_doc);
178 assert(JS_InstanceOf(ctx, parent_win, (JSClass *) &window_class, NULL));
179 if_assert_failed return JS_FALSE;
181 vs = JS_GetInstancePrivate(ctx, parent_win,
182 (JSClass *) &window_class, NULL);
183 doc_view = vs->doc_view;
184 document = doc_view->document;
185 fs = input_get_form_state(ctx, obj);
186 if (!fs) return JS_FALSE; /* detached */
187 fc = find_form_control(document, fs);
189 assert(fc);
190 assert(fc->form && fs);
192 if (!JSID_IS_INT(id))
193 return JS_TRUE;
195 linknum = get_form_control_link(document, fc);
196 /* Hiddens have no link. */
197 if (linknum >= 0) link = &document->links[linknum];
199 undef_to_jsval(ctx, vp);
201 switch (JSID_TO_INT(id)) {
202 case JSP_INPUT_ACCESSKEY:
204 JSString *keystr;
206 if (!link) break;
208 if (!link->accesskey) {
209 *vp = JS_GetEmptyStringValue(ctx);
210 } else {
211 keystr = unicode_to_jsstring(ctx, link->accesskey);
212 if (keystr)
213 *vp = STRING_TO_JSVAL(keystr);
214 else
215 return JS_FALSE;
217 break;
219 case JSP_INPUT_ALT:
220 string_to_jsval(ctx, vp, fc->alt);
221 break;
222 case JSP_INPUT_CHECKED:
223 boolean_to_jsval(ctx, vp, fs->state);
224 break;
225 case JSP_INPUT_DEFAULT_CHECKED:
226 boolean_to_jsval(ctx, vp, fc->default_state);
227 break;
228 case JSP_INPUT_DEFAULT_VALUE:
229 /* FIXME (bug 805): convert from the charset of the document */
230 string_to_jsval(ctx, vp, fc->default_value);
231 break;
232 case JSP_INPUT_DISABLED:
233 /* FIXME: <input readonly disabled> --pasky */
234 boolean_to_jsval(ctx, vp, fc->mode == FORM_MODE_DISABLED);
235 break;
236 case JSP_INPUT_FORM:
237 object_to_jsval(ctx, vp, parent_form);
238 break;
239 case JSP_INPUT_MAX_LENGTH:
240 int_to_jsval(ctx, vp, fc->maxlength);
241 break;
242 case JSP_INPUT_NAME:
243 string_to_jsval(ctx, vp, fc->name);
244 break;
245 case JSP_INPUT_READONLY:
246 /* FIXME: <input readonly disabled> --pasky */
247 boolean_to_jsval(ctx, vp, fc->mode == FORM_MODE_READONLY);
248 break;
249 case JSP_INPUT_SIZE:
250 int_to_jsval(ctx, vp, fc->size);
251 break;
252 case JSP_INPUT_SRC:
253 if (link && link->where_img)
254 string_to_jsval(ctx, vp, link->where_img);
255 break;
256 case JSP_INPUT_TABINDEX:
257 if (link)
258 /* FIXME: This is WRONG. --pasky */
259 int_to_jsval(ctx, vp, link->number);
260 break;
261 case JSP_INPUT_TYPE:
263 unsigned char *s = NULL;
265 switch (fc->type) {
266 case FC_TEXT: s = "text"; break;
267 case FC_PASSWORD: s = "password"; break;
268 case FC_FILE: s = "file"; break;
269 case FC_CHECKBOX: s = "checkbox"; break;
270 case FC_RADIO: s = "radio"; break;
271 case FC_SUBMIT: s = "submit"; break;
272 case FC_IMAGE: s = "image"; break;
273 case FC_RESET: s = "reset"; break;
274 case FC_BUTTON: s = "button"; break;
275 case FC_HIDDEN: s = "hidden"; break;
276 case FC_SELECT: s = "select"; break;
277 default: INTERNAL("input_get_property() upon a non-input item."); break;
279 string_to_jsval(ctx, vp, s);
280 break;
282 case JSP_INPUT_VALUE:
283 string_to_jsval(ctx, vp, fs->value);
284 break;
286 case JSP_INPUT_SELECTED_INDEX:
287 if (fc->type == FC_SELECT) int_to_jsval(ctx, vp, fs->state);
288 break;
289 default:
290 /* Unrecognized integer property ID; someone is using
291 * the object as an array. SMJS builtin classes (e.g.
292 * js_RegExpClass) just return JS_TRUE in this case
293 * and leave *@vp unchanged. Do the same here.
294 * (Actually not quite the same, as we already used
295 * @undef_to_jsval.) */
296 break;
299 return JS_TRUE;
302 /* @input_class.setProperty */
303 static JSBool
304 input_set_property(JSContext *ctx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
306 JSObject *parent_form; /* instance of @form_class */
307 JSObject *parent_doc; /* instance of @document_class */
308 JSObject *parent_win; /* instance of @window_class */
309 struct view_state *vs;
310 struct document_view *doc_view;
311 struct document *document;
312 struct form_state *fs;
313 struct form_control *fc;
314 int linknum;
315 struct link *link = NULL;
316 unicode_val_T accesskey;
318 /* This can be called if @obj if not itself an instance of the
319 * appropriate class but has one in its prototype chain. Fail
320 * such calls. */
321 if (!JS_InstanceOf(ctx, obj, (JSClass *) &input_class, NULL))
322 return JS_FALSE;
323 parent_form = JS_GetParent(ctx, obj);
324 assert(JS_InstanceOf(ctx, parent_form, (JSClass *) &form_class, NULL));
325 if_assert_failed return JS_FALSE;
326 parent_doc = JS_GetParent(ctx, parent_form);
327 assert(JS_InstanceOf(ctx, parent_doc, (JSClass *) &document_class, NULL));
328 if_assert_failed return JS_FALSE;
329 parent_win = JS_GetParent(ctx, parent_doc);
330 assert(JS_InstanceOf(ctx, parent_win, (JSClass *) &window_class, NULL));
331 if_assert_failed return JS_FALSE;
333 vs = JS_GetInstancePrivate(ctx, parent_win,
334 (JSClass *) &window_class, NULL);
335 doc_view = vs->doc_view;
336 document = doc_view->document;
337 fs = input_get_form_state(ctx, obj);
338 if (!fs) return JS_FALSE; /* detached */
339 fc = find_form_control(document, fs);
341 assert(fc);
342 assert(fc->form && fs);
344 if (!JSID_IS_INT(id))
345 return JS_TRUE;
347 linknum = get_form_control_link(document, fc);
348 /* Hiddens have no link. */
349 if (linknum >= 0) link = &document->links[linknum];
351 switch (JSID_TO_INT(id)) {
352 case JSP_INPUT_ACCESSKEY:
353 accesskey = jsval_to_accesskey(ctx, vp);
354 if (accesskey == UCS_NO_CHAR)
355 return JS_FALSE;
356 else if (link)
357 link->accesskey = accesskey;
358 break;
359 case JSP_INPUT_ALT:
360 mem_free_set(&fc->alt, stracpy(jsval_to_string(ctx, vp)));
361 break;
362 case JSP_INPUT_CHECKED:
363 if (fc->type != FC_CHECKBOX && fc->type != FC_RADIO)
364 break;
365 fs->state = jsval_to_boolean(ctx, vp);
366 break;
367 case JSP_INPUT_DISABLED:
368 /* FIXME: <input readonly disabled> --pasky */
369 fc->mode = (jsval_to_boolean(ctx, vp) ? FORM_MODE_DISABLED
370 : fc->mode == FORM_MODE_READONLY ? FORM_MODE_READONLY
371 : FORM_MODE_NORMAL);
372 break;
373 case JSP_INPUT_MAX_LENGTH:
374 if (!JS_ValueToInt32(ctx, *vp, &fc->maxlength))
375 return JS_FALSE;
376 break;
377 case JSP_INPUT_NAME:
378 mem_free_set(&fc->name, stracpy(jsval_to_string(ctx, vp)));
379 break;
380 case JSP_INPUT_READONLY:
381 /* FIXME: <input readonly disabled> --pasky */
382 fc->mode = (jsval_to_boolean(ctx, vp) ? FORM_MODE_READONLY
383 : fc->mode == FORM_MODE_DISABLED ? FORM_MODE_DISABLED
384 : FORM_MODE_NORMAL);
385 break;
386 case JSP_INPUT_SRC:
387 if (link) {
388 mem_free_set(&link->where_img, stracpy(jsval_to_string(ctx, vp)));
390 break;
391 case JSP_INPUT_VALUE:
392 if (fc->type == FC_FILE)
393 break; /* A huge security risk otherwise. */
394 mem_free_set(&fs->value, stracpy(jsval_to_string(ctx, vp)));
395 if (fc->type == FC_TEXT || fc->type == FC_PASSWORD)
396 fs->state = strlen(fs->value);
397 break;
398 case JSP_INPUT_SELECTED_INDEX:
399 if (fc->type == FC_SELECT) {
400 int item;
402 if (!JS_ValueToInt32(ctx, *vp, &item))
403 return JS_FALSE;
405 if (item >= 0 && item < fc->nvalues) {
406 fs->state = item;
407 mem_free_set(&fs->value, stracpy(fc->values[item]));
410 break;
412 default:
413 /* Unrecognized integer property ID; someone is using
414 * the object as an array. SMJS builtin classes (e.g.
415 * js_RegExpClass) just return JS_TRUE in this case.
416 * Do the same here. */
417 return JS_TRUE;
420 return JS_TRUE;
423 /* @input_funcs{"blur"} */
424 static JSBool
425 input_blur(JSContext *ctx, uintN argc, jsval *rval)
427 /* We are a text-mode browser and there *always* has to be something
428 * selected. So we do nothing for now. (That was easy.) */
429 return JS_TRUE;
432 /* @input_funcs{"click"} */
433 static JSBool
434 input_click(JSContext *ctx, uintN argc, jsval *rval)
436 jsval val;
437 JSObject *parent_form; /* instance of @form_class */
438 JSObject *parent_doc; /* instance of @document_class */
439 JSObject *parent_win; /* instance of @window_class */
440 JSObject *obj = JS_THIS_OBJECT(ctx, rval);
441 jsval *argv = JS_ARGV(ctx, rval);
442 struct view_state *vs;
443 struct document_view *doc_view;
444 struct document *document;
445 struct session *ses;
446 struct form_state *fs;
447 struct form_control *fc;
448 int linknum;
450 if (!JS_InstanceOf(ctx, obj, (JSClass *) &input_class, argv)) return JS_FALSE;
451 parent_form = JS_GetParent(ctx, obj);
452 assert(JS_InstanceOf(ctx, parent_form, (JSClass *) &form_class, NULL));
453 if_assert_failed return JS_FALSE;
454 parent_doc = JS_GetParent(ctx, parent_form);
455 assert(JS_InstanceOf(ctx, parent_doc, (JSClass *) &document_class, NULL));
456 if_assert_failed return JS_FALSE;
457 parent_win = JS_GetParent(ctx, parent_doc);
458 assert(JS_InstanceOf(ctx, parent_win, (JSClass *) &window_class, NULL));
459 if_assert_failed return JS_FALSE;
461 vs = JS_GetInstancePrivate(ctx, parent_win,
462 (JSClass *) &window_class, NULL);
463 doc_view = vs->doc_view;
464 document = doc_view->document;
465 ses = doc_view->session;
466 fs = input_get_form_state(ctx, obj);
467 if (!fs) return JS_FALSE; /* detached */
469 assert(fs);
470 fc = find_form_control(document, fs);
471 assert(fc);
473 linknum = get_form_control_link(document, fc);
474 /* Hiddens have no link. */
475 if (linknum < 0)
476 return JS_TRUE;
478 /* Restore old current_link afterwards? */
479 jump_to_link_number(ses, doc_view, linknum);
480 if (enter(ses, doc_view, 0) == FRAME_EVENT_REFRESH)
481 refresh_view(ses, doc_view, 0);
482 else
483 print_screen_status(ses);
485 boolean_to_jsval(ctx, &val, 0);
486 JS_SET_RVAL(ctx, rval, val);
487 return JS_TRUE;
490 /* @input_funcs{"focus"} */
491 static JSBool
492 input_focus(JSContext *ctx, uintN argc, jsval *rval)
494 jsval val;
495 JSObject *parent_form; /* instance of @form_class */
496 JSObject *parent_doc; /* instance of @document_class */
497 JSObject *parent_win; /* instance of @window_class */
498 JSObject *obj = JS_THIS_OBJECT(ctx, rval);
499 jsval *argv = JS_ARGV(ctx, rval);
500 struct view_state *vs;
501 struct document_view *doc_view;
502 struct document *document;
503 struct session *ses;
504 struct form_state *fs;
505 struct form_control *fc;
506 int linknum;
508 if (!JS_InstanceOf(ctx, obj, (JSClass *) &input_class, argv)) return JS_FALSE;
509 parent_form = JS_GetParent(ctx, obj);
510 assert(JS_InstanceOf(ctx, parent_form, (JSClass *) &form_class, NULL));
511 if_assert_failed return JS_FALSE;
512 parent_doc = JS_GetParent(ctx, parent_form);
513 assert(JS_InstanceOf(ctx, parent_doc, (JSClass *) &document_class, NULL));
514 if_assert_failed return JS_FALSE;
515 parent_win = JS_GetParent(ctx, parent_doc);
516 assert(JS_InstanceOf(ctx, parent_win, (JSClass *) &window_class, NULL));
517 if_assert_failed return JS_FALSE;
519 vs = JS_GetInstancePrivate(ctx, parent_win,
520 (JSClass *) &window_class, NULL);
521 doc_view = vs->doc_view;
522 document = doc_view->document;
523 ses = doc_view->session;
524 fs = input_get_form_state(ctx, obj);
525 if (!fs) return JS_FALSE; /* detached */
527 assert(fs);
528 fc = find_form_control(document, fs);
529 assert(fc);
531 linknum = get_form_control_link(document, fc);
532 /* Hiddens have no link. */
533 if (linknum < 0)
534 return JS_TRUE;
536 jump_to_link_number(ses, doc_view, linknum);
538 boolean_to_jsval(ctx, &val, 0);
539 JS_SET_RVAL(ctx, rval, val);
540 return JS_TRUE;
543 /* @input_funcs{"select"} */
544 static JSBool
545 input_select(JSContext *ctx, uintN argc, jsval *rval)
547 /* We support no text selecting yet. So we do nothing for now.
548 * (That was easy, too.) */
549 return JS_TRUE;
552 static JSObject *
553 get_input_object(JSContext *ctx, JSObject *jsform, struct form_state *fs)
555 JSObject *jsinput = fs->ecmascript_obj;
557 if (jsinput) {
558 /* This assumes JS_GetInstancePrivate cannot GC. */
559 assert(JS_GetInstancePrivate(ctx, jsinput,
560 (JSClass *) &input_class, NULL)
561 == fs);
562 if_assert_failed return NULL;
564 return jsinput;
567 /* jsform ('form') is input's parent */
568 /* FIXME: That is NOT correct since the real containing element
569 * should be its parent, but gimme DOM first. --pasky */
570 jsinput = JS_NewObject(ctx, (JSClass *) &input_class, NULL, jsform);
571 if (!jsinput)
572 return NULL;
573 JS_DefineProperties(ctx, jsinput, (JSPropertySpec *) input_props);
574 spidermonkey_DefineFunctions(ctx, jsinput, input_funcs);
576 if (!JS_SetPrivate(ctx, jsinput, fs)) /* to @input_class */
577 return NULL;
578 fs->ecmascript_obj = jsinput;
579 return jsinput;
582 static void
583 input_finalize(JSContext *ctx, JSObject *jsinput)
585 struct form_state *fs = JS_GetInstancePrivate(ctx, jsinput,
586 (JSClass *) &input_class,
587 NULL);
589 if (fs) {
590 /* If this assertion fails, leave fs->ecmascript_obj
591 * unchanged, because it may point to a different
592 * JSObject whose private pointer will later have to
593 * be updated to avoid crashes. */
594 assert(fs->ecmascript_obj == jsinput);
595 if_assert_failed return;
597 fs->ecmascript_obj = NULL;
598 /* No need to JS_SetPrivate, because jsinput is being
599 * destroyed. */
603 void
604 spidermonkey_detach_form_state(struct form_state *fs)
606 JSObject *jsinput = fs->ecmascript_obj;
608 if (jsinput) {
609 /* This assumes JS_GetInstancePrivate and JS_SetPrivate
610 * cannot GC. */
612 /* If this assertion fails, it is not clear whether
613 * the private pointer of jsinput should be reset;
614 * crashes seem possible either way. Resetting it is
615 * easiest. */
616 assert(JS_GetInstancePrivate(spidermonkey_empty_context,
617 jsinput,
618 (JSClass *) &input_class, NULL)
619 == fs);
620 if_assert_failed {}
622 JS_SetPrivate(spidermonkey_empty_context, jsinput, NULL);
623 fs->ecmascript_obj = NULL;
627 void
628 spidermonkey_moved_form_state(struct form_state *fs)
630 JSObject *jsinput = fs->ecmascript_obj;
632 if (jsinput) {
633 /* This assumes JS_SetPrivate cannot GC. If it could,
634 * then the GC might call input_finalize for some
635 * other object whose struct form_state has also been
636 * reallocated, and an assertion would fail in
637 * input_finalize. */
638 JS_SetPrivate(spidermonkey_empty_context, jsinput, fs);
643 static JSObject *
644 get_form_control_object(JSContext *ctx, JSObject *jsform,
645 enum form_type type, struct form_state *fs)
647 switch (type) {
648 case FC_TEXT:
649 case FC_PASSWORD:
650 case FC_FILE:
651 case FC_CHECKBOX:
652 case FC_RADIO:
653 case FC_SUBMIT:
654 case FC_IMAGE:
655 case FC_RESET:
656 case FC_BUTTON:
657 case FC_HIDDEN:
658 case FC_SELECT:
659 return get_input_object(ctx, jsform, fs);
661 case FC_TEXTAREA:
662 /* TODO */
663 return NULL;
665 default:
666 INTERNAL("Weird fc->type %d", type);
667 return NULL;
672 static struct form_view *form_get_form_view(JSContext *ctx, JSObject *jsform, jsval *argv);
673 static JSBool form_elements_get_property(JSContext *ctx, JSObject *obj, jsid id, jsval *vp);
675 /* Each @form_elements_class object must have a @form_class parent. */
676 static const JSClass form_elements_class = {
677 "elements",
678 JSCLASS_HAS_PRIVATE,
679 JS_PropertyStub, JS_PropertyStub,
680 form_elements_get_property, JS_StrictPropertyStub,
681 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub
684 static JSBool form_elements_item2(JSContext *ctx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
685 static JSBool form_elements_namedItem2(JSContext *ctx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
686 static JSBool form_elements_item(JSContext *ctx, uintN argc, jsval *rval);
687 static JSBool form_elements_namedItem(JSContext *ctx, uintN argc, jsval *rval);
690 static const spidermonkeyFunctionSpec form_elements_funcs[] = {
691 { "item", form_elements_item, 1 },
692 { "namedItem", form_elements_namedItem, 1 },
693 { NULL }
696 /* Tinyids of properties. Use negative values to distinguish these
697 * from array indexes (elements[INT] for INT>=0 is equivalent to
698 * elements.item(INT)). ECMAScript code should not use these directly
699 * as in elements[-1]; future versions of ELinks may change the numbers. */
700 enum form_elements_prop {
701 JSP_FORM_ELEMENTS_LENGTH = -1,
703 static const JSPropertySpec form_elements_props[] = {
704 { "length", JSP_FORM_ELEMENTS_LENGTH, JSPROP_ENUMERATE | JSPROP_READONLY},
705 { NULL }
708 /* @form_elements_class.getProperty */
709 static JSBool
710 form_elements_get_property(JSContext *ctx, JSObject *obj, jsid id, jsval *vp)
712 jsval idval;
713 JSObject *parent_form; /* instance of @form_class */
714 JSObject *parent_doc; /* instance of @document_class */
715 JSObject *parent_win; /* instance of @window_class */
716 struct view_state *vs;
717 struct document_view *doc_view;
718 struct document *document;
719 struct form_view *form_view;
720 struct form *form;
722 /* This can be called if @obj if not itself an instance of the
723 * appropriate class but has one in its prototype chain. Fail
724 * such calls. */
725 if (!JS_InstanceOf(ctx, obj, (JSClass *) &form_elements_class, NULL))
726 return JS_FALSE;
727 parent_form = JS_GetParent(ctx, obj);
728 assert(JS_InstanceOf(ctx, parent_form, (JSClass *) &form_class, NULL));
729 if_assert_failed return JS_FALSE;
730 parent_doc = JS_GetParent(ctx, parent_form);
731 assert(JS_InstanceOf(ctx, parent_doc, (JSClass *) &document_class, NULL));
732 if_assert_failed return JS_FALSE;
733 parent_win = JS_GetParent(ctx, parent_doc);
734 assert(JS_InstanceOf(ctx, parent_win, (JSClass *) &window_class, NULL));
735 if_assert_failed return JS_FALSE;
737 vs = JS_GetInstancePrivate(ctx, parent_win,
738 (JSClass *) &window_class, NULL);
739 doc_view = vs->doc_view;
740 document = doc_view->document;
741 form_view = form_get_form_view(ctx, parent_form, NULL);
742 if (!form_view) return JS_FALSE; /* detached */
743 form = find_form_by_form_view(document, form_view);
745 if (JSID_IS_STRING(id)) {
746 JS_IdToValue(ctx, id, &idval);
747 form_elements_namedItem2(ctx, obj, 1, &idval, vp);
748 return JS_TRUE;
751 if (!JSID_IS_INT(id))
752 return JS_TRUE;
754 undef_to_jsval(ctx, vp);
756 switch (JSID_TO_INT(id)) {
757 case JSP_FORM_ELEMENTS_LENGTH:
758 int_to_jsval(ctx, vp, list_size(&form->items));
759 break;
760 default:
761 /* Array index. */
762 JS_IdToValue(ctx, id, &idval);
763 form_elements_item2(ctx, obj, 1, &idval, vp);
764 break;
767 return JS_TRUE;
770 /* @form_elements_funcs{"item"} */
771 static JSBool
772 form_elements_item(JSContext *ctx, uintN argc, jsval *rval)
774 jsval val = JSVAL_VOID;
775 JSObject *obj = JS_THIS_OBJECT(ctx, rval);
776 jsval *argv = JS_ARGV(ctx, rval);
777 JSBool ret = form_elements_item2(ctx, obj, argc, argv, &val);
779 JS_SET_RVAL(ctx, rval, val);
780 return ret;
783 static JSBool
784 form_elements_item2(JSContext *ctx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
786 JSObject *parent_form; /* instance of @form_class */
787 JSObject *parent_doc; /* instance of @document_class */
788 JSObject *parent_win; /* instance of @window_class */
789 struct view_state *vs;
790 struct document_view *doc_view;
791 struct document *document;
792 struct form_view *form_view;
793 struct form *form;
794 struct form_control *fc;
795 int counter = -1;
796 int index;
798 if (!JS_InstanceOf(ctx, obj, (JSClass *) &form_elements_class, argv)) return JS_FALSE;
799 parent_form = JS_GetParent(ctx, obj);
800 assert(JS_InstanceOf(ctx, parent_form, (JSClass *) &form_class, NULL));
801 if_assert_failed return JS_FALSE;
802 parent_doc = JS_GetParent(ctx, parent_form);
803 assert(JS_InstanceOf(ctx, parent_doc, (JSClass *) &document_class, NULL));
804 if_assert_failed return JS_FALSE;
805 parent_win = JS_GetParent(ctx, parent_doc);
806 assert(JS_InstanceOf(ctx, parent_win, (JSClass *) &window_class, NULL));
807 if_assert_failed return JS_FALSE;
809 vs = JS_GetInstancePrivate(ctx, parent_win,
810 (JSClass *) &window_class, NULL);
811 doc_view = vs->doc_view;
812 document = doc_view->document;
813 form_view = form_get_form_view(ctx, parent_form, NULL);
814 if (!form_view) return JS_FALSE; /* detached */
815 form = find_form_by_form_view(document, form_view);
817 if (argc != 1)
818 return JS_TRUE;
820 if (!JS_ValueToInt32(ctx, argv[0], &index))
821 return JS_FALSE;
822 undef_to_jsval(ctx, rval);
824 foreach (fc, form->items) {
825 counter++;
826 if (counter == index) {
827 struct form_state *fs = find_form_state(doc_view, fc);
829 if (fs) {
830 JSObject *fcobj = get_form_control_object(ctx, parent_form, fc->type, fs);
832 if (fcobj)
833 object_to_jsval(ctx, rval, fcobj);
835 break;
839 return JS_TRUE;
842 /* @form_elements_funcs{"namedItem"} */
843 static JSBool
844 form_elements_namedItem(JSContext *ctx, uintN argc, jsval *rval)
846 jsval val = JSVAL_VOID;
847 JSObject *obj = JS_THIS_OBJECT(ctx, rval);
848 jsval *argv = JS_ARGV(ctx, rval);
849 JSBool ret = form_elements_namedItem2(ctx, obj, argc, argv, &val);
851 JS_SET_RVAL(ctx, rval, val);
852 return ret;
855 static JSBool
856 form_elements_namedItem2(JSContext *ctx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
858 JSObject *parent_form; /* instance of @form_class */
859 JSObject *parent_doc; /* instance of @document_class */
860 JSObject *parent_win; /* instance of @window_class */
861 struct view_state *vs;
862 struct document_view *doc_view;
863 struct document *document;
864 struct form_view *form_view;
865 struct form *form;
866 struct form_control *fc;
867 unsigned char *string;
869 if (!JS_InstanceOf(ctx, obj, (JSClass *) &form_elements_class, argv)) return JS_FALSE;
870 parent_form = JS_GetParent(ctx, obj);
871 assert(JS_InstanceOf(ctx, parent_form, (JSClass *) &form_class, NULL));
872 if_assert_failed return JS_FALSE;
873 parent_doc = JS_GetParent(ctx, parent_form);
874 assert(JS_InstanceOf(ctx, parent_doc, (JSClass *) &document_class, NULL));
875 if_assert_failed return JS_FALSE;
876 parent_win = JS_GetParent(ctx, parent_doc);
877 assert(JS_InstanceOf(ctx, parent_win, (JSClass *) &window_class, NULL));
878 if_assert_failed return JS_FALSE;
880 vs = JS_GetInstancePrivate(ctx, parent_win,
881 (JSClass *) &window_class, NULL);
882 doc_view = vs->doc_view;
883 document = doc_view->document;
884 form_view = form_get_form_view(ctx, parent_form, NULL);
885 if (!form_view) return JS_FALSE; /* detached */
886 form = find_form_by_form_view(document, form_view);
888 if (argc != 1)
889 return JS_TRUE;
891 string = jsval_to_string(ctx, &argv[0]);
892 if (!*string)
893 return JS_TRUE;
895 undef_to_jsval(ctx, rval);
897 foreach (fc, form->items) {
898 if ((fc->id && !c_strcasecmp(string, fc->id))
899 || (fc->name && !c_strcasecmp(string, fc->name))) {
900 struct form_state *fs = find_form_state(doc_view, fc);
902 if (fs) {
903 JSObject *fcobj = get_form_control_object(ctx, parent_form, fc->type, fs);
905 if (fcobj)
906 object_to_jsval(ctx, rval, fcobj);
908 break;
912 return JS_TRUE;
917 static JSBool form_get_property(JSContext *ctx, JSObject *obj, jsid id, jsval *vp);
918 static JSBool form_set_property(JSContext *ctx, JSObject *obj, jsid id, JSBool strict, jsval *vp);
919 static void form_finalize(JSContext *ctx, JSObject *obj);
921 /* Each @form_class object must have a @document_class parent. */
922 static const JSClass form_class = {
923 "form",
924 JSCLASS_HAS_PRIVATE, /* struct form_view *, or NULL if detached */
925 JS_PropertyStub, JS_PropertyStub,
926 form_get_property, form_set_property,
927 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, form_finalize
930 /* Tinyids of properties. Use negative values to distinguish these
931 * from array indexes (even though this object has no array elements).
932 * ECMAScript code should not use these directly as in form[-1];
933 * future versions of ELinks may change the numbers. */
934 enum form_prop {
935 JSP_FORM_ACTION = -1,
936 JSP_FORM_ELEMENTS = -2,
937 JSP_FORM_ENCODING = -3,
938 JSP_FORM_LENGTH = -4,
939 JSP_FORM_METHOD = -5,
940 JSP_FORM_NAME = -6,
941 JSP_FORM_TARGET = -7,
944 static const JSPropertySpec form_props[] = {
945 { "action", JSP_FORM_ACTION, JSPROP_ENUMERATE },
946 { "elements", JSP_FORM_ELEMENTS, JSPROP_ENUMERATE },
947 { "encoding", JSP_FORM_ENCODING, JSPROP_ENUMERATE },
948 { "length", JSP_FORM_LENGTH, JSPROP_ENUMERATE | JSPROP_READONLY },
949 { "method", JSP_FORM_METHOD, JSPROP_ENUMERATE },
950 { "name", JSP_FORM_NAME, JSPROP_ENUMERATE },
951 { "target", JSP_FORM_TARGET, JSPROP_ENUMERATE },
952 { NULL }
955 static JSBool form_reset(JSContext *ctx, uintN argc, jsval *rval);
956 static JSBool form_submit(JSContext *ctx, uintN argc, jsval *rval);
958 static const spidermonkeyFunctionSpec form_funcs[] = {
959 { "reset", form_reset, 0 },
960 { "submit", form_submit, 0 },
961 { NULL }
964 static struct form_view *
965 form_get_form_view(JSContext *ctx, JSObject *jsform, jsval *argv)
967 struct form_view *fv = JS_GetInstancePrivate(ctx, jsform,
968 (JSClass *) &form_class,
969 argv);
971 if (!fv) return NULL; /* detached */
973 assert(fv->ecmascript_obj == jsform);
974 if_assert_failed return NULL;
976 return fv;
979 /* @form_class.getProperty */
980 static JSBool
981 form_get_property(JSContext *ctx, JSObject *obj, jsid id, jsval *vp)
983 /* DBG("doc %p %s\n", parent_doc, JS_GetStringBytes(JS_ValueToString(ctx, OBJECT_TO_JSVAL(parent_doc)))); */
984 JSObject *parent_doc; /* instance of @document_class */
985 JSObject *parent_win; /* instance of @window_class */
986 struct view_state *vs;
987 struct document_view *doc_view;
988 struct form_view *fv;
989 struct form *form;
991 /* This can be called if @obj if not itself an instance of the
992 * appropriate class but has one in its prototype chain. Fail
993 * such calls. */
994 if (!JS_InstanceOf(ctx, obj, (JSClass *) &form_class, NULL))
995 return JS_FALSE;
996 parent_doc = JS_GetParent(ctx, obj);
997 assert(JS_InstanceOf(ctx, parent_doc, (JSClass *) &document_class, NULL));
998 if_assert_failed return JS_FALSE;
999 parent_win = JS_GetParent(ctx, parent_doc);
1000 assert(JS_InstanceOf(ctx, parent_win, (JSClass *) &window_class, NULL));
1001 if_assert_failed return JS_FALSE;
1003 vs = JS_GetInstancePrivate(ctx, parent_win,
1004 (JSClass *) &window_class, NULL);
1005 doc_view = vs->doc_view;
1006 fv = form_get_form_view(ctx, obj, NULL);
1007 if (!fv) return JS_FALSE; /* detached */
1008 form = find_form_by_form_view(doc_view->document, fv);
1010 assert(form);
1012 if (JSID_IS_STRING(id)) {
1013 struct form_control *fc;
1014 unsigned char *string;
1016 string = jsid_to_string(ctx, &id);
1017 foreach (fc, form->items) {
1018 JSObject *fcobj = NULL;
1019 struct form_state *fs;
1021 if ((!fc->id || c_strcasecmp(string, fc->id))
1022 && (!fc->name || c_strcasecmp(string, fc->name)))
1023 continue;
1025 undef_to_jsval(ctx, vp);
1026 fs = find_form_state(doc_view, fc);
1027 if (fs) {
1028 fcobj = get_form_control_object(ctx, obj, fc->type, fs);
1029 if (fcobj)
1030 object_to_jsval(ctx, vp, fcobj);
1032 break;
1034 return JS_TRUE;
1037 if (!JSID_IS_INT(id))
1038 return JS_TRUE;
1040 undef_to_jsval(ctx, vp);
1042 switch (JSID_TO_INT(id)) {
1043 case JSP_FORM_ACTION:
1044 string_to_jsval(ctx, vp, form->action);
1045 break;
1047 case JSP_FORM_ELEMENTS:
1049 /* jsform ('form') is form_elements' parent; who knows is that's correct */
1050 JSObject *jsform_elems = JS_NewObject(ctx, (JSClass *) &form_elements_class, NULL, obj);
1052 JS_DefineProperties(ctx, jsform_elems, (JSPropertySpec *) form_elements_props);
1053 spidermonkey_DefineFunctions(ctx, jsform_elems,
1054 form_elements_funcs);
1055 object_to_jsval(ctx, vp, jsform_elems);
1056 /* SM will cache this property value for us so we create this
1057 * just once per form. */
1059 break;
1061 case JSP_FORM_ENCODING:
1062 switch (form->method) {
1063 case FORM_METHOD_GET:
1064 case FORM_METHOD_POST:
1065 string_to_jsval(ctx, vp, "application/x-www-form-urlencoded");
1066 break;
1067 case FORM_METHOD_POST_MP:
1068 string_to_jsval(ctx, vp, "multipart/form-data");
1069 break;
1070 case FORM_METHOD_POST_TEXT_PLAIN:
1071 string_to_jsval(ctx, vp, "text/plain");
1072 break;
1074 break;
1076 case JSP_FORM_LENGTH:
1077 int_to_jsval(ctx, vp, list_size(&form->items));
1078 break;
1080 case JSP_FORM_METHOD:
1081 switch (form->method) {
1082 case FORM_METHOD_GET:
1083 string_to_jsval(ctx, vp, "GET");
1084 break;
1086 case FORM_METHOD_POST:
1087 case FORM_METHOD_POST_MP:
1088 case FORM_METHOD_POST_TEXT_PLAIN:
1089 string_to_jsval(ctx, vp, "POST");
1090 break;
1092 break;
1094 case JSP_FORM_NAME:
1095 string_to_jsval(ctx, vp, form->name);
1096 break;
1098 case JSP_FORM_TARGET:
1099 string_to_jsval(ctx, vp, form->target);
1100 break;
1102 default:
1103 /* Unrecognized integer property ID; someone is using
1104 * the object as an array. SMJS builtin classes (e.g.
1105 * js_RegExpClass) just return JS_TRUE in this case
1106 * and leave *@vp unchanged. Do the same here.
1107 * (Actually not quite the same, as we already used
1108 * @undef_to_jsval.) */
1109 break;
1112 return JS_TRUE;
1115 /* @form_class.setProperty */
1116 static JSBool
1117 form_set_property(JSContext *ctx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
1119 JSObject *parent_doc; /* instance of @document_class */
1120 JSObject *parent_win; /* instance of @window_class */
1121 struct view_state *vs;
1122 struct document_view *doc_view;
1123 struct form_view *fv;
1124 struct form *form;
1125 unsigned char *string;
1127 /* This can be called if @obj if not itself an instance of the
1128 * appropriate class but has one in its prototype chain. Fail
1129 * such calls. */
1130 if (!JS_InstanceOf(ctx, obj, (JSClass *) &form_class, NULL))
1131 return JS_FALSE;
1132 parent_doc = JS_GetParent(ctx, obj);
1133 assert(JS_InstanceOf(ctx, parent_doc, (JSClass *) &document_class, NULL));
1134 if_assert_failed return JS_FALSE;
1135 parent_win = JS_GetParent(ctx, parent_doc);
1136 assert(JS_InstanceOf(ctx, parent_win, (JSClass *) &window_class, NULL));
1137 if_assert_failed return JS_FALSE;
1139 vs = JS_GetInstancePrivate(ctx, parent_win,
1140 (JSClass *) &window_class, NULL);
1141 doc_view = vs->doc_view;
1142 fv = form_get_form_view(ctx, obj, NULL);
1143 if (!fv) return JS_FALSE; /* detached */
1144 form = find_form_by_form_view(doc_view->document, fv);
1146 assert(form);
1148 if (!JSID_IS_INT(id))
1149 return JS_TRUE;
1151 switch (JSID_TO_INT(id)) {
1152 case JSP_FORM_ACTION:
1153 string = stracpy(jsval_to_string(ctx, vp));
1154 if (form->action) {
1155 ecmascript_set_action(&form->action, string);
1156 } else {
1157 mem_free_set(&form->action, string);
1159 break;
1161 case JSP_FORM_ENCODING:
1162 string = jsval_to_string(ctx, vp);
1163 if (!c_strcasecmp(string, "application/x-www-form-urlencoded")) {
1164 form->method = form->method == FORM_METHOD_GET ? FORM_METHOD_GET
1165 : FORM_METHOD_POST;
1166 } else if (!c_strcasecmp(string, "multipart/form-data")) {
1167 form->method = FORM_METHOD_POST_MP;
1168 } else if (!c_strcasecmp(string, "text/plain")) {
1169 form->method = FORM_METHOD_POST_TEXT_PLAIN;
1171 break;
1173 case JSP_FORM_METHOD:
1174 string = jsval_to_string(ctx, vp);
1175 if (!c_strcasecmp(string, "GET")) {
1176 form->method = FORM_METHOD_GET;
1177 } else if (!c_strcasecmp(string, "POST")) {
1178 form->method = FORM_METHOD_POST;
1180 break;
1182 case JSP_FORM_NAME:
1183 mem_free_set(&form->name, stracpy(jsval_to_string(ctx, vp)));
1184 break;
1186 case JSP_FORM_TARGET:
1187 mem_free_set(&form->target, stracpy(jsval_to_string(ctx, vp)));
1188 break;
1190 default:
1191 /* Unrecognized integer property ID; someone is using
1192 * the object as an array. SMJS builtin classes (e.g.
1193 * js_RegExpClass) just return JS_TRUE in this case.
1194 * Do the same here. */
1195 break;
1198 return JS_TRUE;
1201 /* @form_funcs{"reset"} */
1202 static JSBool
1203 form_reset(JSContext *ctx, uintN argc, jsval *rval)
1205 jsval val;
1206 JSObject *parent_doc; /* instance of @document_class */
1207 JSObject *parent_win; /* instance of @window_class */
1208 JSObject *obj = JS_THIS_OBJECT(ctx, rval);
1209 jsval *argv = JS_ARGV(ctx, rval);
1210 struct view_state *vs;
1211 struct document_view *doc_view;
1212 struct form_view *fv;
1213 struct form *form;
1215 if (!JS_InstanceOf(ctx, obj, (JSClass *) &form_class, argv)) return JS_FALSE;
1216 parent_doc = JS_GetParent(ctx, obj);
1217 assert(JS_InstanceOf(ctx, parent_doc, (JSClass *) &document_class, NULL));
1218 if_assert_failed return JS_FALSE;
1219 parent_win = JS_GetParent(ctx, parent_doc);
1220 assert(JS_InstanceOf(ctx, parent_win, (JSClass *) &window_class, NULL));
1221 if_assert_failed return JS_FALSE;
1223 vs = JS_GetInstancePrivate(ctx, parent_win,
1224 (JSClass *) &window_class, NULL);
1225 doc_view = vs->doc_view;
1226 fv = form_get_form_view(ctx, obj, argv);
1227 if (!fv) return JS_FALSE; /* detached */
1228 form = find_form_by_form_view(doc_view->document, fv);
1230 assert(form);
1232 do_reset_form(doc_view, form);
1233 draw_forms(doc_view->session->tab->term, doc_view);
1235 boolean_to_jsval(ctx, &val, 0);
1236 JS_SET_RVAL(ctx, rval, val);
1238 return JS_TRUE;
1241 /* @form_funcs{"submit"} */
1242 static JSBool
1243 form_submit(JSContext *ctx, uintN argc, jsval *rval)
1245 jsval val;
1246 JSObject *parent_doc; /* instance of @document_class */
1247 JSObject *parent_win; /* instance of @window_class */
1248 JSObject *obj = JS_THIS_OBJECT(ctx, rval);
1249 jsval *argv = JS_ARGV(ctx, rval);
1250 struct view_state *vs;
1251 struct document_view *doc_view;
1252 struct session *ses;
1253 struct form_view *fv;
1254 struct form *form;
1256 if (!JS_InstanceOf(ctx, obj, (JSClass *) &form_class, argv)) return JS_FALSE;
1257 parent_doc = JS_GetParent(ctx, obj);
1258 assert(JS_InstanceOf(ctx, parent_doc, (JSClass *) &document_class, NULL));
1259 if_assert_failed return JS_FALSE;
1260 parent_win = JS_GetParent(ctx, parent_doc);
1261 assert(JS_InstanceOf(ctx, parent_win, (JSClass *) &window_class, NULL));
1262 if_assert_failed return JS_FALSE;
1264 vs = JS_GetInstancePrivate(ctx, parent_win,
1265 (JSClass *) &window_class, NULL);
1266 doc_view = vs->doc_view;
1267 ses = doc_view->session;
1268 fv = form_get_form_view(ctx, obj, argv);
1269 if (!fv) return JS_FALSE; /* detached */
1270 form = find_form_by_form_view(doc_view->document, fv);
1272 assert(form);
1273 submit_given_form(ses, doc_view, form, 0);
1275 boolean_to_jsval(ctx, &val, 0);
1276 JS_SET_RVAL(ctx, rval, val);
1278 return JS_TRUE;
1281 JSObject *
1282 get_form_object(JSContext *ctx, JSObject *jsdoc, struct form_view *fv)
1284 JSObject *jsform = fv->ecmascript_obj;
1286 if (jsform) {
1287 /* This assumes JS_GetInstancePrivate cannot GC. */
1288 assert(JS_GetInstancePrivate(ctx, jsform,
1289 (JSClass *) &form_class, NULL)
1290 == fv);
1291 if_assert_failed return NULL;
1293 return jsform;
1296 /* jsdoc ('document') is fv's parent */
1297 /* FIXME: That is NOT correct since the real containing element
1298 * should be its parent, but gimme DOM first. --pasky */
1299 jsform = JS_NewObject(ctx, (JSClass *) &form_class, NULL, jsdoc);
1300 if (jsform == NULL)
1301 return NULL;
1302 JS_DefineProperties(ctx, jsform, (JSPropertySpec *) form_props);
1303 spidermonkey_DefineFunctions(ctx, jsform, form_funcs);
1305 if (!JS_SetPrivate(ctx, jsform, fv)) /* to @form_class */
1306 return NULL;
1307 fv->ecmascript_obj = jsform;
1308 return jsform;
1311 static void
1312 form_finalize(JSContext *ctx, JSObject *jsform)
1314 struct form_view *fv = JS_GetInstancePrivate(ctx, jsform,
1315 (JSClass *) &form_class,
1316 NULL);
1318 if (fv) {
1319 /* If this assertion fails, leave fv->ecmascript_obj
1320 * unchanged, because it may point to a different
1321 * JSObject whose private pointer will later have to
1322 * be updated to avoid crashes. */
1323 assert(fv->ecmascript_obj == jsform);
1324 if_assert_failed return;
1326 fv->ecmascript_obj = NULL;
1327 /* No need to JS_SetPrivate, because the object is
1328 * being destroyed. */
1332 void
1333 spidermonkey_detach_form_view(struct form_view *fv)
1335 JSObject *jsform = fv->ecmascript_obj;
1337 if (jsform) {
1338 /* This assumes JS_GetInstancePrivate and JS_SetPrivate
1339 * cannot GC. */
1341 /* If this assertion fails, it is not clear whether
1342 * the private pointer of jsform should be reset;
1343 * crashes seem possible either way. Resetting it is
1344 * easiest. */
1345 assert(JS_GetInstancePrivate(spidermonkey_empty_context,
1346 jsform,
1347 (JSClass *) &form_class, NULL)
1348 == fv);
1349 if_assert_failed {}
1351 JS_SetPrivate(spidermonkey_empty_context, jsform, NULL);
1352 fv->ecmascript_obj = NULL;
1357 static JSBool forms_get_property(JSContext *ctx, JSObject *obj, jsid id, jsval *vp);
1359 /* Each @forms_class object must have a @document_class parent. */
1360 const JSClass forms_class = {
1361 "forms",
1362 JSCLASS_HAS_PRIVATE,
1363 JS_PropertyStub, JS_PropertyStub,
1364 forms_get_property, JS_StrictPropertyStub,
1365 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub
1368 static JSBool forms_item(JSContext *ctx, uintN argc, jsval *rval);
1369 static JSBool forms_item2(JSContext *ctx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
1370 static JSBool forms_namedItem(JSContext *ctx, uintN argc, jsval *rval);
1372 const spidermonkeyFunctionSpec forms_funcs[] = {
1373 { "item", forms_item, 1 },
1374 { "namedItem", forms_namedItem, 1 },
1375 { NULL }
1378 /* Tinyids of properties. Use negative values to distinguish these from
1379 * array indexes (forms[INT] for INT>=0 is equivalent to forms.item(INT)).
1380 * ECMAScript code should not use these directly as in forms[-1];
1381 * future versions of ELinks may change the numbers. */
1382 enum forms_prop {
1383 JSP_FORMS_LENGTH = -1,
1385 const JSPropertySpec forms_props[] = {
1386 { "length", JSP_FORMS_LENGTH, JSPROP_ENUMERATE | JSPROP_READONLY},
1387 { NULL }
1390 /* Find the form whose name is @name, which should normally be a
1391 * string (but might not be). If found, set *rval = the DOM
1392 * object. If not found, leave *rval unchanged. */
1393 static void
1394 find_form_by_name(JSContext *ctx, JSObject *jsdoc,
1395 struct document_view *doc_view,
1396 jsval name, jsval *rval)
1398 unsigned char *string = jsval_to_string(ctx, &name);
1399 struct form *form;
1401 if (!*string)
1402 return;
1404 foreach (form, doc_view->document->forms) {
1405 if (form->name && !c_strcasecmp(string, form->name)) {
1406 object_to_jsval(ctx, rval, get_form_object(ctx, jsdoc,
1407 find_form_view(doc_view, form)));
1408 break;
1413 /* @forms_class.getProperty */
1414 static JSBool
1415 forms_get_property(JSContext *ctx, JSObject *obj, jsid id, jsval *vp)
1417 jsval idval;
1418 JSObject *parent_doc; /* instance of @document_class */
1419 JSObject *parent_win; /* instance of @window_class */
1420 struct view_state *vs;
1421 struct document_view *doc_view;
1422 struct document *document;
1424 /* This can be called if @obj if not itself an instance of the
1425 * appropriate class but has one in its prototype chain. Fail
1426 * such calls. */
1427 if (!JS_InstanceOf(ctx, obj, (JSClass *) &forms_class, NULL))
1428 return JS_FALSE;
1429 parent_doc = JS_GetParent(ctx, obj);
1430 assert(JS_InstanceOf(ctx, parent_doc, (JSClass *) &document_class, NULL));
1431 if_assert_failed return JS_FALSE;
1432 parent_win = JS_GetParent(ctx, parent_doc);
1433 assert(JS_InstanceOf(ctx, parent_win, (JSClass *) &window_class, NULL));
1434 if_assert_failed return JS_FALSE;
1436 vs = JS_GetInstancePrivate(ctx, parent_win,
1437 (JSClass *) &window_class, NULL);
1438 doc_view = vs->doc_view;
1439 document = doc_view->document;
1441 if (JSID_IS_STRING(id)) {
1442 /* When SMJS evaluates forms.namedItem("foo"), it first
1443 * calls forms_get_property with id = JSString "namedItem"
1444 * and *vp = JSObject JSFunction forms_namedItem.
1445 * If we don't find a form whose name is id,
1446 * we must leave *vp unchanged here, to avoid
1447 * "TypeError: forms.namedItem is not a function". */
1448 JS_IdToValue(ctx, id, &idval);
1449 find_form_by_name(ctx, parent_doc, doc_view, idval, vp);
1450 return JS_TRUE;
1453 if (!JSID_IS_INT(id))
1454 return JS_TRUE;
1456 switch (JSID_TO_INT(id)) {
1457 case JSP_FORMS_LENGTH:
1458 int_to_jsval(ctx, vp, list_size(&document->forms));
1459 break;
1460 default:
1461 /* Array index. */
1462 JS_IdToValue(ctx, id, &idval);
1463 forms_item2(ctx, obj, 1, &idval, vp);
1464 break;
1467 return JS_TRUE;
1470 /* @forms_funcs{"item"} */
1471 static JSBool
1472 forms_item(JSContext *ctx, uintN argc, jsval *rval)
1474 jsval val = JSVAL_VOID;
1475 JSObject *obj = JS_THIS_OBJECT(ctx, rval);
1476 jsval *argv = JS_ARGV(ctx, rval);
1477 JSBool ret = forms_item2(ctx, obj, argc, argv, &val);
1479 JS_SET_RVAL(ctx, rval, val);
1480 return ret;
1483 static JSBool
1484 forms_item2(JSContext *ctx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
1486 JSObject *parent_doc; /* instance of @document_class */
1487 JSObject *parent_win; /* instance of @window_class */
1488 struct view_state *vs;
1489 struct form_view *fv;
1490 int counter = -1;
1491 int index;
1493 if (!JS_InstanceOf(ctx, obj, (JSClass *) &forms_class, argv)) return JS_FALSE;
1494 parent_doc = JS_GetParent(ctx, obj);
1495 assert(JS_InstanceOf(ctx, parent_doc, (JSClass *) &document_class, NULL));
1496 if_assert_failed return JS_FALSE;
1497 parent_win = JS_GetParent(ctx, parent_doc);
1498 assert(JS_InstanceOf(ctx, parent_win, (JSClass *) &window_class, NULL));
1499 if_assert_failed return JS_FALSE;
1501 vs = JS_GetInstancePrivate(ctx, parent_win,
1502 (JSClass *) &window_class, NULL);
1504 if (argc != 1)
1505 return JS_TRUE;
1507 if (!JS_ValueToInt32(ctx, argv[0], &index))
1508 return JS_FALSE;
1509 undef_to_jsval(ctx, rval);
1511 foreach (fv, vs->forms) {
1512 counter++;
1513 if (counter == index) {
1514 object_to_jsval(ctx, rval, get_form_object(ctx, parent_doc, fv));
1515 break;
1519 return JS_TRUE;
1522 /* @forms_funcs{"namedItem"} */
1523 static JSBool
1524 forms_namedItem(JSContext *ctx, uintN argc, jsval *rval)
1526 jsval val;
1527 JSObject *parent_doc; /* instance of @document_class */
1528 JSObject *parent_win; /* instance of @window_class */
1529 JSObject *obj = JS_THIS_OBJECT(ctx, rval);
1530 jsval *argv = JS_ARGV(ctx, rval);
1531 struct view_state *vs;
1532 struct document_view *doc_view;
1534 if (!JS_InstanceOf(ctx, obj, (JSClass *) &forms_class, argv)) return JS_FALSE;
1535 parent_doc = JS_GetParent(ctx, obj);
1536 assert(JS_InstanceOf(ctx, parent_doc, (JSClass *) &document_class, NULL));
1537 if_assert_failed return JS_FALSE;
1538 parent_win = JS_GetParent(ctx, parent_doc);
1539 assert(JS_InstanceOf(ctx, parent_win, (JSClass *) &window_class, NULL));
1540 if_assert_failed return JS_FALSE;
1542 vs = JS_GetInstancePrivate(ctx, parent_win,
1543 (JSClass *) &window_class, NULL);
1544 doc_view = vs->doc_view;
1546 if (argc != 1)
1547 return JS_TRUE;
1549 undef_to_jsval(ctx, &val);
1550 find_form_by_name(ctx, parent_doc, doc_view, argv[0], &val);
1551 JS_SET_RVAL(ctx, rval, val);
1552 return JS_TRUE;
1556 static JSString *
1557 unicode_to_jsstring(JSContext *ctx, unicode_val_T u)
1559 jschar buf[2];
1561 /* This is supposed to make a string from which
1562 * jsval_to_accesskey() can get the original @u back.
1563 * If @u is a surrogate, then that is not possible, so
1564 * return NULL to indicate an error instead.
1566 * If JS_NewUCStringCopyN hits a null character, it truncates
1567 * the string there and pads it with more nulls. However,
1568 * that is not a problem here, because if there is a null
1569 * character in buf[], then it must be the only character. */
1570 if (u <= 0xFFFF && !is_utf16_surrogate(u)) {
1571 buf[0] = u;
1572 return JS_NewUCStringCopyN(ctx, buf, 1);
1573 } else if (needs_utf16_surrogates(u)) {
1574 buf[0] = get_utf16_high_surrogate(u);
1575 buf[1] = get_utf16_low_surrogate(u);
1576 return JS_NewUCStringCopyN(ctx, buf, 2);
1577 } else {
1578 return NULL;
1582 /* Convert the string *@vp to an access key. Return 0 for no access
1583 * key, UCS_NO_CHAR on error, or the access key otherwise. */
1584 static unicode_val_T
1585 jsval_to_accesskey(JSContext *ctx, jsval *vp)
1587 size_t len;
1588 const char *chr;
1590 /* Convert the value in place, to protect the result from GC. */
1591 if (JS_ConvertValue(ctx, *vp, JSTYPE_STRING, vp) == JS_FALSE)
1592 return UCS_NO_CHAR;
1593 len = JS_GetStringEncodingLength(ctx, JSVAL_TO_STRING(*vp));
1594 chr = JS_EncodeString(ctx, JSVAL_TO_STRING(*vp));
1596 /* This implementation ignores extra characters in the string. */
1597 if (len < 1)
1598 return 0; /* which means no access key */
1599 if (!is_utf16_surrogate(chr[0]))
1600 return chr[0];
1601 if (len >= 2
1602 && is_utf16_high_surrogate(chr[0])
1603 && is_utf16_low_surrogate(chr[1]))
1604 return join_utf16_surrogates(chr[0], chr[1]);
1605 JS_ReportError(ctx, "Invalid UTF-16 sequence");
1606 return UCS_NO_CHAR; /* which the caller will reject */