Cast to (const char *) in strrchr calls
[elinks.git] / src / scripting / smjs / session_object.c
blob89a15dae14ed57dc95f3a6378e7e7493c4ab5492
1 /* Exports struct session to the world of ECMAScript */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <stdlib.h>
8 #include <stdio.h>
9 #include <string.h>
11 #include "elinks.h"
13 #include "ecmascript/spidermonkey/util.h"
14 #include "main/select.h"
15 #include "protocol/uri.h"
16 #include "scripting/smjs/core.h"
17 #include "scripting/smjs/elinks_object.h"
18 #include "scripting/smjs/global_object.h"
19 #include "scripting/smjs/session_object.h"
20 #include "scripting/smjs/view_state_object.h"
21 #include "session/history.h"
22 #include "session/location.h"
23 #include "session/session.h"
24 #include "session/task.h"
25 #include "terminal/tab.h"
26 #include "util/error.h"
27 #include "util/memory.h"
28 #include "viewer/text/vs.h"
31 static JSObject *smjs_session_object;
33 static const JSClass session_class; /* Defined below. */
35 static const JSClass location_array_class; /* Defined below. */
37 /* location_array_class is the class for array object, the elements of which
38 * correspond to the elements of session.history.
40 * session_class.history returns a location_array_class object, so define
41 * location_array_class and related routines before session_class. */
43 /* @location_array.getProperty */
44 static JSBool
45 smjs_location_array_get_property(JSContext *ctx, JSObject *obj, jsid id, jsval *vp)
47 struct session *ses;
48 int index;
49 struct location *loc;
51 /* This can be called if @obj if not itself an instance of the
52 * appropriate class but has one in its prototype chain. Fail
53 * such calls. */
54 if (!JS_InstanceOf(ctx, obj, (JSClass *) &location_array_class, NULL))
55 return JS_FALSE;
57 ses = JS_GetInstancePrivate(ctx, obj,
58 (JSClass *) &location_array_class, NULL);
59 if (!ses) return JS_FALSE;
61 undef_to_jsval(ctx, vp);
63 if (!JSID_IS_INT(id))
64 return JS_FALSE;
66 assert(ses);
67 if_assert_failed return JS_TRUE;
69 if (!have_location(ses)) return JS_FALSE;
71 index = JSID_TO_INT(id);
72 for (loc = cur_loc(ses);
73 loc != (struct location *) &ses->history.history;
74 loc = index > 0 ? loc->next : loc->prev) {
75 if (!index) {
76 JSObject *obj = smjs_get_view_state_object(&loc->vs);
78 if (obj) object_to_jsval(ctx, vp, obj);
80 return JS_TRUE;
83 index += index > 0 ? -1 : 1;
86 return JS_FALSE;
89 /** Pointed to by location_array_class.finalize. SpiderMonkey automatically
90 * finalizes all objects before it frees the JSRuntime, so
91 * session.history_jsobject won't be left dangling. */
92 static void
93 smjs_location_array_finalize(JSContext *ctx, JSObject *obj)
95 struct session *ses;
97 assert(JS_InstanceOf(ctx, obj, (JSClass *) &location_array_class, NULL));
98 if_assert_failed return;
100 ses = JS_GetInstancePrivate(ctx, obj,
101 (JSClass *) &location_array_class, NULL);
103 if (!ses) return; /* already detached */
105 JS_SetPrivate(ctx, obj, NULL); /* perhaps not necessary */
106 assert(ses->history_jsobject == obj);
107 if_assert_failed return;
108 ses->history_jsobject = NULL;
111 static const JSClass location_array_class = {
112 "location_array",
113 JSCLASS_HAS_PRIVATE, /* struct session *; a weak reference */
114 JS_PropertyStub, JS_PropertyStub,
115 smjs_location_array_get_property, JS_StrictPropertyStub,
116 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, smjs_location_array_finalize,
119 /** Return an SMJS object through which scripts can access @a ses.history. If
120 * there already is such an object, return that; otherwise create a new one.
121 * The SMJS object holds only a weak reference to @a ses. */
122 JSObject *
123 smjs_get_session_location_array_object(struct session *ses)
125 JSObject *obj;
127 if (ses->history_jsobject) return ses->history_jsobject;
129 assert(smjs_ctx);
130 if_assert_failed return NULL;
132 obj = JS_NewObject(smjs_ctx, (JSClass *) &location_array_class, NULL, NULL);
133 if (!obj) return NULL;
135 /* Do this last, so that if any previous step fails, we can
136 * just forget the object and its finalizer won't attempt to
137 * access @ses. */
138 if (JS_FALSE == JS_SetPrivate(smjs_ctx, obj, ses))
139 return NULL;
141 ses->history_jsobject = obj;
142 return obj;
145 /* There is no smjs_detach_session_location_array_object because
146 * smjs_detach_session_object detaches both session.jsobject and
147 * session.history.js_object. */
150 enum session_prop {
151 SESSION_VISITED,
152 SESSION_HISTORY,
153 SESSION_LOADING_URI,
154 SESSION_RELOADLEVEL,
155 SESSION_REDIRECT_CNT,
156 /* XXX: SESSION_DOC_VIEW, */
157 /* XXX: SESSION_FRAMES, */
158 SESSION_SEARCH_DIRECTION,
159 SESSION_KBDPREFIX,
160 SESSION_MARK_WAITING_FOR,
161 SESSION_EXIT_QUERY,
162 SESSION_INSERT_MODE,
163 SESSION_NAVIGATE_MODE,
164 SESSION_SEARCH_WORD,
165 SESSION_LAST_SEARCH_WORD,
166 /* XXX: SESSION_TYPE_QUERIES, */
169 static const JSPropertySpec session_props[] = {
170 { "visited", SESSION_VISITED, JSPROP_ENUMERATE },
171 { "history", SESSION_HISTORY, JSPROP_ENUMERATE | JSPROP_READONLY },
172 { "loading_uri", SESSION_LOADING_URI, JSPROP_ENUMERATE | JSPROP_READONLY },
173 { "reloadlevel", SESSION_RELOADLEVEL, JSPROP_ENUMERATE },
174 { "redirect_cnt", SESSION_REDIRECT_CNT, JSPROP_ENUMERATE },
175 /* XXX: { "doc_view", SESSION_DOC_VIEW, JSPROP_ENUMERATE | JSPROP_READONLY }, */
176 /* XXX: { "frames", SESSION_FRAMES, JSPROP_ENUMERATE | JSPROP_READONLY }, */
177 { "search_direction", SESSION_SEARCH_DIRECTION, JSPROP_ENUMERATE },
178 { "kbdprefix", SESSION_KBDPREFIX, JSPROP_ENUMERATE },
179 { "mark", SESSION_MARK_WAITING_FOR, JSPROP_ENUMERATE },
180 { "exit_query", SESSION_EXIT_QUERY, JSPROP_ENUMERATE | JSPROP_READONLY },
181 { "insert_mode", SESSION_INSERT_MODE, JSPROP_ENUMERATE },
182 { "navigate_mode", SESSION_NAVIGATE_MODE, JSPROP_ENUMERATE },
183 { "search_word", SESSION_SEARCH_WORD, JSPROP_ENUMERATE },
184 { "last_search_word", SESSION_LAST_SEARCH_WORD, JSPROP_ENUMERATE },
185 /* XXX: { "type_queries", SESSION_TYPE_QUERIES, JSPROP_ENUMERATE }, */
186 { NULL }
189 /* @session_class.getProperty */
190 static JSBool
191 session_get_property(JSContext *ctx, JSObject *obj, jsid id, jsval *vp)
193 struct session *ses;
195 /* This can be called if @obj if not itself an instance of the
196 * appropriate class but has one in its prototype chain. Fail
197 * such calls. */
198 if (!JS_InstanceOf(ctx, obj, (JSClass *) &session_class, NULL))
199 return JS_FALSE;
201 ses = JS_GetInstancePrivate(ctx, obj,
202 (JSClass *) &session_class, NULL);
203 if (!ses) return JS_FALSE;
205 if (!JSID_IS_INT(id)) {
206 /* Note: If we return JS_FALSE here, the object's methods do not
207 * work. */
208 return JS_TRUE;
211 /* XXX: Lock session here if it is ever changed to have an OBJECT_HEAD. */
213 undef_to_jsval(ctx, vp);
215 switch (JSID_TO_INT(id)) {
216 case SESSION_VISITED:
217 int_to_jsval(ctx, vp, ses->status.visited);
219 return JS_TRUE;
220 case SESSION_HISTORY: {
221 JSObject *obj = smjs_get_session_location_array_object(ses);
223 if (obj) object_to_jsval(ctx, vp, obj);
225 return JS_TRUE;
227 case SESSION_LOADING_URI: {
228 struct uri *uri = have_location(ses) ? cur_loc(ses)->vs.uri
229 : ses->loading_uri;
231 if (uri) string_to_jsval(ctx, vp, struri(uri));
233 return JS_TRUE;
235 case SESSION_RELOADLEVEL:
236 int_to_jsval(ctx, vp, ses->reloadlevel);
238 return JS_TRUE;
239 case SESSION_REDIRECT_CNT:
240 int_to_jsval(ctx, vp, ses->redirect_cnt);
242 return JS_TRUE;
243 case SESSION_SEARCH_DIRECTION:
244 string_to_jsval(ctx, vp, ses->search_direction == 1 ? "down"
245 : "up");
247 return JS_TRUE;
248 case SESSION_KBDPREFIX:
249 int_to_jsval(ctx, vp, ses->kbdprefix.repeat_count);
251 return JS_TRUE;
252 case SESSION_MARK_WAITING_FOR:
253 string_to_jsval(ctx, vp, ses->kbdprefix.mark == KP_MARK_NOTHING
254 ? "nothing"
255 : ses->kbdprefix.mark == KP_MARK_SET
256 ? "set"
257 : "goto");
259 return JS_TRUE;
260 case SESSION_EXIT_QUERY:
261 int_to_jsval(ctx, vp, ses->exit_query);
263 return JS_TRUE;
264 case SESSION_INSERT_MODE:
265 string_to_jsval(ctx, vp,
266 ses->insert_mode == INSERT_MODE_LESS
267 ? "disabled"
268 : ses->insert_mode == INSERT_MODE_ON
269 ? "on"
270 : "off");
272 return JS_TRUE;
273 case SESSION_NAVIGATE_MODE:
274 string_to_jsval(ctx, vp,
275 ses->navigate_mode == NAVIGATE_CURSOR_ROUTING
276 ? "cursor"
277 : "linkwise");
279 return JS_TRUE;
280 case SESSION_SEARCH_WORD:
281 string_to_jsval(ctx, vp, ses->search_word);
283 return JS_TRUE;
284 case SESSION_LAST_SEARCH_WORD:
285 string_to_jsval(ctx, vp, ses->last_search_word);
287 return JS_TRUE;
288 default:
289 INTERNAL("Invalid ID %d in session_get_property().",
290 JSID_TO_INT(id));
293 return JS_FALSE;
296 static JSBool
297 session_set_property(JSContext *ctx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
299 struct session *ses;
301 /* This can be called if @obj if not itself an instance of the
302 * appropriate class but has one in its prototype chain. Fail
303 * such calls. */
304 if (!JS_InstanceOf(ctx, obj, (JSClass *) &session_class, NULL))
305 return JS_FALSE;
307 ses = JS_GetInstancePrivate(ctx, obj,
308 (JSClass *) &session_class, NULL);
309 if (!ses) return JS_FALSE;
311 if (!JSID_IS_INT(id))
312 return JS_FALSE;
314 switch (JSID_TO_INT(id)) {
315 case SESSION_VISITED:
316 ses->status.visited = atol(jsval_to_string(ctx, vp));
318 return JS_TRUE;
319 /* SESSION_HISTORY is RO */
320 /* SESSION_LOADING_URI is RO */
321 case SESSION_RELOADLEVEL:
322 ses->reloadlevel = atol(jsval_to_string(ctx, vp));
324 return JS_TRUE;
325 case SESSION_REDIRECT_CNT:
326 ses->redirect_cnt = atol(jsval_to_string(ctx, vp));
328 return JS_TRUE;
329 case SESSION_SEARCH_DIRECTION: {
330 unsigned char *str;
331 JSString *jsstr;
333 jsstr = JS_ValueToString(ctx, *vp);
334 if (!jsstr) return JS_TRUE;
336 str = JS_EncodeString(ctx, jsstr);
337 if (!str) return JS_TRUE;
339 if (!strcmp(str, "up"))
340 ses->search_direction = -1;
341 else if (!strcmp(str, "down"))
342 ses->search_direction = 1;
343 else
344 return JS_FALSE;
346 return JS_TRUE;
348 case SESSION_KBDPREFIX:
349 ses->kbdprefix.repeat_count = atol(jsval_to_string(ctx, vp));
351 return JS_TRUE;
352 case SESSION_MARK_WAITING_FOR: {
353 unsigned char *str;
354 JSString *jsstr;
356 jsstr = JS_ValueToString(ctx, *vp);
357 if (!jsstr) return JS_TRUE;
359 str = JS_EncodeString(ctx, jsstr);
360 if (!str) return JS_TRUE;
362 if (!strcmp(str, "nothing"))
363 ses->kbdprefix.mark = KP_MARK_NOTHING;
364 else if (!strcmp(str, "set"))
365 ses->kbdprefix.mark = KP_MARK_SET;
366 else if (!strcmp(str, "goto"))
367 ses->kbdprefix.mark = KP_MARK_GOTO;
368 else
369 return JS_FALSE;
371 return JS_TRUE;
373 /* SESSION_EXIT_QUERY is RO */
374 case SESSION_INSERT_MODE: {
375 unsigned char *str;
376 JSString *jsstr;
378 jsstr = JS_ValueToString(ctx, *vp);
379 if (!jsstr) return JS_TRUE;
381 str = JS_EncodeString(ctx, jsstr);
382 if (!str) return JS_TRUE;
384 if (!strcmp(str, "disabled"))
385 ses->insert_mode = INSERT_MODE_LESS;
386 else if (!strcmp(str, "on"))
387 ses->insert_mode = INSERT_MODE_ON;
388 else if (!strcmp(str, "off"))
389 ses->insert_mode = INSERT_MODE_OFF;
390 else
391 return JS_FALSE;
393 return JS_TRUE;
395 case SESSION_NAVIGATE_MODE: {
396 unsigned char *str;
397 JSString *jsstr;
399 jsstr = JS_ValueToString(ctx, *vp);
400 if (!jsstr) return JS_TRUE;
402 str = JS_EncodeString(ctx, jsstr);
403 if (!str) return JS_TRUE;
405 if (!strcmp(str, "cursor"))
406 ses->navigate_mode = NAVIGATE_CURSOR_ROUTING;
407 else if (!strcmp(str, "linkwise"))
408 ses->navigate_mode = NAVIGATE_LINKWISE;
409 else
410 return JS_FALSE;
412 return JS_TRUE;
414 case SESSION_SEARCH_WORD: {
415 unsigned char *str;
416 JSString *jsstr;
418 jsstr = JS_ValueToString(ctx, *vp);
419 if (!jsstr) return JS_TRUE;
421 str = JS_EncodeString(ctx, jsstr);
422 if (!str) return JS_TRUE;
424 mem_free_set(&ses->search_word, str);
426 return JS_TRUE;
428 case SESSION_LAST_SEARCH_WORD: {
429 unsigned char *str;
430 JSString *jsstr;
432 jsstr = JS_ValueToString(ctx, *vp);
433 if (!jsstr) return JS_TRUE;
435 str = JS_EncodeString(ctx, jsstr);
436 if (!str) return JS_TRUE;
438 mem_free_set(&ses->last_search_word, str);
440 return JS_TRUE;
442 default:
443 INTERNAL("Invalid ID %d in session_set_property().",
444 JSID_TO_INT(id));
447 return JS_FALSE;
450 /** Pointed to by session_class.construct. Create a new session (tab)
451 * and return the JSObject wrapper. */
452 static JSBool
453 session_construct(JSContext *ctx, uintN argc, jsval *rval)
455 jsval val;
456 jsval *argv = JS_ARGV(ctx, rval);
457 int bg = 0; /* open new tab in background */
458 struct session *ses;
459 JSObject *jsobj;
461 if (argc > 1) {
462 return JS_TRUE;
465 if (argc >= 1) {
466 bg = jsval_to_boolean(ctx, &argv[0]);
469 if (!smjs_ses) return JS_FALSE;
471 ses = init_session(smjs_ses, smjs_ses->tab->term, NULL, bg);
472 if (!ses) return JS_FALSE;
474 jsobj = smjs_get_session_object(ses);
475 if (!jsobj) return JS_FALSE;
477 object_to_jsval(ctx, &val, jsobj);
478 JS_SET_RVAL(ctx, rval, val);
480 return JS_TRUE;
483 /** Pointed to by session_class.finalize. SpiderMonkey automatically
484 * finalizes all objects before it frees the JSRuntime, so session.jsobject
485 * won't be left dangling. */
486 static void
487 session_finalize(JSContext *ctx, JSObject *obj)
489 struct session *ses;
491 assert(JS_InstanceOf(ctx, obj, (JSClass *) &session_class, NULL));
492 if_assert_failed return;
494 ses = JS_GetInstancePrivate(ctx, obj,
495 (JSClass *) &session_class, NULL);
497 if (!ses) return; /* already detached */
499 JS_SetPrivate(ctx, obj, NULL); /* perhaps not necessary */
500 assert(ses->jsobject == obj);
501 if_assert_failed return;
502 ses->jsobject = NULL;
505 static const JSClass session_class = {
506 "session",
507 JSCLASS_HAS_PRIVATE, /* struct session *; a weak reference */
508 JS_PropertyStub, JS_PropertyStub,
509 session_get_property, session_set_property,
510 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, session_finalize,
511 NULL, NULL, session_construct
514 /** Return an SMJS object through which scripts can access @a ses.
515 * If there already is such an object, return that; otherwise create a
516 * new one. The SMJS object holds only a weak reference to @a ses. */
517 JSObject *
518 smjs_get_session_object(struct session *ses)
520 JSObject *obj;
522 if (ses->jsobject) return ses->jsobject;
524 assert(smjs_ctx);
525 if_assert_failed return NULL;
527 obj = JS_NewObject(smjs_ctx, (JSClass *) &session_class, NULL, NULL);
528 if (!obj) return NULL;
530 if (JS_FALSE == JS_DefineProperties(smjs_ctx, obj,
531 (JSPropertySpec *) session_props))
532 return NULL;
534 /* Do this last, so that if any previous step fails, we can
535 * just forget the object and its finalizer won't attempt to
536 * access @ses. */
537 if (JS_FALSE == JS_SetPrivate(smjs_ctx, obj, ses)) /* to @session_class */
538 return NULL;
540 ses->jsobject = obj;
541 return obj;
544 /** Ensure that no JSObject contains the pointer @a ses. This is
545 * called from destroy_session before @a ses is freed. If a JSObject was
546 * previously attached to the session object, the object will remain in
547 * memory but it will no longer be able to access the session object. */
548 void
549 smjs_detach_session_object(struct session *ses)
551 assert(smjs_ctx);
552 assert(ses);
553 if_assert_failed return;
555 if (ses->jsobject) {
556 assert(JS_GetInstancePrivate(smjs_ctx, ses->jsobject,
557 (JSClass *) &session_class, NULL)
558 == ses);
559 if_assert_failed {}
561 JS_SetPrivate(smjs_ctx, ses->jsobject, NULL);
562 ses->jsobject = NULL;
565 if (ses->history_jsobject) {
566 assert(JS_GetInstancePrivate(smjs_ctx, ses->history_jsobject,
567 (JSClass *) &location_array_class,
568 NULL)
569 == ses);
570 if_assert_failed {}
572 JS_SetPrivate(smjs_ctx, ses->history_jsobject, NULL);
573 ses->history_jsobject = NULL;
578 /** Ensure that no JSObject contains the pointer @a ses. This is
579 * called when the reference count of the session object *@a ses is
580 * already 0 and it is about to be freed. If a JSObject was
581 * previously attached to the session object, the object will remain in
582 * memory but it will no longer be able to access the session object. */
583 static JSBool
584 session_array_get_property(JSContext *ctx, JSObject *obj, jsid id, jsval *vp)
586 JSObject *tabobj;
587 struct terminal *term = JS_GetPrivate(ctx, obj);
588 int index;
589 struct window *tab;
591 undef_to_jsval(ctx, vp);
593 if (!JSID_IS_INT(id))
594 return JS_FALSE;
596 assert(term);
597 if_assert_failed return JS_TRUE;
599 index = JSID_TO_INT(id);
600 foreach_tab (tab, term->windows) {
601 if (!index) break;
602 --index;
604 if ((void *) tab == (void *) &term->windows) return JS_FALSE;
606 tabobj = smjs_get_session_object(tab->data);
607 if (tabobj) object_to_jsval(ctx, vp, tabobj);
609 return JS_TRUE;
612 static const JSClass session_array_class = {
613 "session_array",
614 JSCLASS_HAS_PRIVATE, /* struct terminal *term; a weak reference */
615 JS_PropertyStub, JS_PropertyStub,
616 session_array_get_property, JS_StrictPropertyStub,
617 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub
620 JSObject *
621 smjs_get_session_array_object(struct terminal *term)
623 JSObject *obj;
625 assert(smjs_ctx);
626 if_assert_failed return NULL;
628 obj = JS_NewObject(smjs_ctx, (JSClass *) &session_array_class,
629 NULL, NULL);
630 if (!obj) return NULL;
632 if (JS_FALSE == JS_SetPrivate(smjs_ctx, obj, term))
633 return NULL;
635 return obj;
638 /** Ensure that no JSObject contains the pointer @a term. This is called from
639 * smjs_detach_terminal_object. If a JSObject was previously attached to the
640 * terminal object, the object will remain in memory but it will no longer be
641 * able to access the terminal object. */
642 void
643 smjs_detach_session_array_object(struct terminal *term)
645 assert(smjs_ctx);
646 assert(term);
647 if_assert_failed return;
649 if (!term->session_array_jsobject) return;
651 assert(JS_GetInstancePrivate(smjs_ctx, term->session_array_jsobject,
652 (JSClass *) &session_array_class, NULL)
653 == term);
654 if_assert_failed {}
656 JS_SetPrivate(smjs_ctx, term->session_array_jsobject, NULL);
657 term->session_array_jsobject = NULL;
660 static JSBool
661 smjs_session_goto_url(JSContext *ctx, uintN argc, jsval *rval)
663 jsval val;
664 struct delayed_open *deo;
665 struct uri *uri;
666 jsval *argv = JS_ARGV(ctx, rval);
667 JSString *jsstr;
668 unsigned char *url;
669 struct session *ses;
670 struct JSObject *this;
672 if (argc != 1) return JS_FALSE;
674 this = JS_THIS_OBJECT(ctx, rval);
675 if (!JS_InstanceOf(ctx, this, (JSClass *) &session_class, NULL))
676 return JS_FALSE;
678 ses = JS_GetInstancePrivate(ctx, this,
679 (JSClass *) &session_class, NULL);
680 if (!ses) return JS_FALSE; /* detached */
682 jsstr = JS_ValueToString(ctx, argv[0]);
683 if (!jsstr) return JS_FALSE;
685 url = JS_EncodeString(ctx, jsstr);
686 if (!url) return JS_FALSE;
688 uri = get_uri(url, 0);
689 if (!uri) return JS_FALSE;
691 deo = mem_calloc(1, sizeof(*deo));
692 if (!deo) {
693 done_uri(uri);
694 return JS_FALSE;
697 deo->ses = ses;
698 deo->uri = get_uri_reference(uri);
699 /* deo->target = NULL; */
700 register_bottom_half(delayed_goto_uri_frame, deo);
702 undef_to_jsval(ctx, &val);
703 JS_SET_RVAL(ctx, rval, val);
705 done_uri(uri);
707 return JS_TRUE;
710 static const spidermonkeyFunctionSpec session_funcs[] = {
711 { "goto", smjs_session_goto_url, 1 },
712 { NULL }
715 void
716 smjs_init_session_interface(void)
718 assert(smjs_ctx);
719 assert(smjs_global_object);
721 smjs_session_object =
722 spidermonkey_InitClass(smjs_ctx, smjs_global_object, NULL,
723 (JSClass *) &session_class, session_construct,
724 0, (JSPropertySpec *) session_props,
725 session_funcs, NULL, NULL);