1 /* Exports struct session to the world of ECMAScript */
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 */
45 smjs_location_array_get_property(JSContext
*ctx
, JSObject
*obj
, jsid id
, jsval
*vp
)
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
54 if (!JS_InstanceOf(ctx
, obj
, (JSClass
*) &location_array_class
, NULL
))
57 ses
= JS_GetInstancePrivate(ctx
, obj
,
58 (JSClass
*) &location_array_class
, NULL
);
59 if (!ses
) return JS_FALSE
;
61 undef_to_jsval(ctx
, vp
);
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
) {
76 JSObject
*obj
= smjs_get_view_state_object(&loc
->vs
);
78 if (obj
) object_to_jsval(ctx
, vp
, obj
);
83 index
+= index
> 0 ? -1 : 1;
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. */
93 smjs_location_array_finalize(JSContext
*ctx
, JSObject
*obj
)
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
= {
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. */
123 smjs_get_session_location_array_object(struct session
*ses
)
127 if (ses
->history_jsobject
) return ses
->history_jsobject
;
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
138 if (JS_FALSE
== JS_SetPrivate(smjs_ctx
, obj
, ses
))
141 ses
->history_jsobject
= 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. */
155 SESSION_REDIRECT_CNT
,
156 /* XXX: SESSION_DOC_VIEW, */
157 /* XXX: SESSION_FRAMES, */
158 SESSION_SEARCH_DIRECTION
,
160 SESSION_MARK_WAITING_FOR
,
163 SESSION_NAVIGATE_MODE
,
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 }, */
189 /* @session_class.getProperty */
191 session_get_property(JSContext
*ctx
, JSObject
*obj
, jsid id
, jsval
*vp
)
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
198 if (!JS_InstanceOf(ctx
, obj
, (JSClass
*) &session_class
, NULL
))
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
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
);
220 case SESSION_HISTORY
: {
221 JSObject
*obj
= smjs_get_session_location_array_object(ses
);
223 if (obj
) object_to_jsval(ctx
, vp
, obj
);
227 case SESSION_LOADING_URI
: {
228 struct uri
*uri
= have_location(ses
) ? cur_loc(ses
)->vs
.uri
231 if (uri
) string_to_jsval(ctx
, vp
, struri(uri
));
235 case SESSION_RELOADLEVEL
:
236 int_to_jsval(ctx
, vp
, ses
->reloadlevel
);
239 case SESSION_REDIRECT_CNT
:
240 int_to_jsval(ctx
, vp
, ses
->redirect_cnt
);
243 case SESSION_SEARCH_DIRECTION
:
244 string_to_jsval(ctx
, vp
, ses
->search_direction
== 1 ? "down"
248 case SESSION_KBDPREFIX
:
249 int_to_jsval(ctx
, vp
, ses
->kbdprefix
.repeat_count
);
252 case SESSION_MARK_WAITING_FOR
:
253 string_to_jsval(ctx
, vp
, ses
->kbdprefix
.mark
== KP_MARK_NOTHING
255 : ses
->kbdprefix
.mark
== KP_MARK_SET
260 case SESSION_EXIT_QUERY
:
261 int_to_jsval(ctx
, vp
, ses
->exit_query
);
264 case SESSION_INSERT_MODE
:
265 string_to_jsval(ctx
, vp
,
266 ses
->insert_mode
== INSERT_MODE_LESS
268 : ses
->insert_mode
== INSERT_MODE_ON
273 case SESSION_NAVIGATE_MODE
:
274 string_to_jsval(ctx
, vp
,
275 ses
->navigate_mode
== NAVIGATE_CURSOR_ROUTING
280 case SESSION_SEARCH_WORD
:
281 string_to_jsval(ctx
, vp
, ses
->search_word
);
284 case SESSION_LAST_SEARCH_WORD
:
285 string_to_jsval(ctx
, vp
, ses
->last_search_word
);
289 INTERNAL("Invalid ID %d in session_get_property().",
297 session_set_property(JSContext
*ctx
, JSObject
*obj
, jsid id
, JSBool strict
, jsval
*vp
)
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
304 if (!JS_InstanceOf(ctx
, obj
, (JSClass
*) &session_class
, NULL
))
307 ses
= JS_GetInstancePrivate(ctx
, obj
,
308 (JSClass
*) &session_class
, NULL
);
309 if (!ses
) return JS_FALSE
;
311 if (!JSID_IS_INT(id
))
314 switch (JSID_TO_INT(id
)) {
315 case SESSION_VISITED
:
316 ses
->status
.visited
= atol(jsval_to_string(ctx
, vp
));
319 /* SESSION_HISTORY is RO */
320 /* SESSION_LOADING_URI is RO */
321 case SESSION_RELOADLEVEL
:
322 ses
->reloadlevel
= atol(jsval_to_string(ctx
, vp
));
325 case SESSION_REDIRECT_CNT
:
326 ses
->redirect_cnt
= atol(jsval_to_string(ctx
, vp
));
329 case SESSION_SEARCH_DIRECTION
: {
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;
348 case SESSION_KBDPREFIX
:
349 ses
->kbdprefix
.repeat_count
= atol(jsval_to_string(ctx
, vp
));
352 case SESSION_MARK_WAITING_FOR
: {
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
;
373 /* SESSION_EXIT_QUERY is RO */
374 case SESSION_INSERT_MODE
: {
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
;
395 case SESSION_NAVIGATE_MODE
: {
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
;
414 case SESSION_SEARCH_WORD
: {
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
);
428 case SESSION_LAST_SEARCH_WORD
: {
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
);
443 INTERNAL("Invalid ID %d in session_set_property().",
450 /** Pointed to by session_class.construct. Create a new session (tab)
451 * and return the JSObject wrapper. */
453 session_construct(JSContext
*ctx
, uintN argc
, jsval
*rval
)
456 jsval
*argv
= JS_ARGV(ctx
, rval
);
457 int bg
= 0; /* open new tab in background */
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
);
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. */
487 session_finalize(JSContext
*ctx
, JSObject
*obj
)
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
= {
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. */
518 smjs_get_session_object(struct session
*ses
)
522 if (ses
->jsobject
) return ses
->jsobject
;
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
))
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
537 if (JS_FALSE
== JS_SetPrivate(smjs_ctx
, obj
, ses
)) /* to @session_class */
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. */
549 smjs_detach_session_object(struct session
*ses
)
553 if_assert_failed
return;
556 assert(JS_GetInstancePrivate(smjs_ctx
, ses
->jsobject
,
557 (JSClass
*) &session_class
, NULL
)
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
,
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. */
584 session_array_get_property(JSContext
*ctx
, JSObject
*obj
, jsid id
, jsval
*vp
)
587 struct terminal
*term
= JS_GetPrivate(ctx
, obj
);
591 undef_to_jsval(ctx
, vp
);
593 if (!JSID_IS_INT(id
))
597 if_assert_failed
return JS_TRUE
;
599 index
= JSID_TO_INT(id
);
600 foreach_tab (tab
, term
->windows
) {
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
);
612 static const JSClass session_array_class
= {
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
621 smjs_get_session_array_object(struct terminal
*term
)
626 if_assert_failed
return NULL
;
628 obj
= JS_NewObject(smjs_ctx
, (JSClass
*) &session_array_class
,
630 if (!obj
) return NULL
;
632 if (JS_FALSE
== JS_SetPrivate(smjs_ctx
, obj
, term
))
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. */
643 smjs_detach_session_array_object(struct terminal
*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
)
656 JS_SetPrivate(smjs_ctx
, term
->session_array_jsobject
, NULL
);
657 term
->session_array_jsobject
= NULL
;
661 smjs_session_goto_url(JSContext
*ctx
, uintN argc
, jsval
*rval
)
664 struct delayed_open
*deo
;
666 jsval
*argv
= JS_ARGV(ctx
, rval
);
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
))
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
));
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
);
710 static const spidermonkeyFunctionSpec session_funcs
[] = {
711 { "goto", smjs_session_goto_url
, 1 },
716 smjs_init_session_interface(void)
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
);