1 /* The SpiderMonkey window object implementation. */
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/window.h"
26 #include "intl/gettext/libintl.h"
27 #include "main/select.h"
28 #include "osdep/newwin.h"
29 #include "osdep/sysname.h"
30 #include "protocol/http/http.h"
31 #include "protocol/uri.h"
32 #include "session/history.h"
33 #include "session/location.h"
34 #include "session/session.h"
35 #include "session/task.h"
36 #include "terminal/tab.h"
37 #include "terminal/terminal.h"
38 #include "util/conv.h"
39 #include "util/memory.h"
40 #include "util/string.h"
41 #include "viewer/text/draw.h"
42 #include "viewer/text/form.h"
43 #include "viewer/text/link.h"
44 #include "viewer/text/vs.h"
47 static JSBool
window_get_property(JSContext
*ctx
, JSObject
*obj
, jsid id
, jsval
*vp
);
48 static JSBool
window_set_property(JSContext
*ctx
, JSObject
*obj
, jsid id
, JSBool strict
, jsval
*vp
);
50 const JSClass window_class
= {
52 JSCLASS_HAS_PRIVATE
| JSCLASS_GLOBAL_FLAGS
, /* struct view_state * */
53 JS_PropertyStub
, JS_PropertyStub
,
54 window_get_property
, window_set_property
,
55 JS_EnumerateStub
, JS_ResolveStub
, JS_ConvertStub
, JS_FinalizeStub
59 /* Tinyids of properties. Use negative values to distinguish these
60 * from array indexes (even though this object has no array elements).
61 * ECMAScript code should not use these directly as in window[-1];
62 * future versions of ELinks may change the numbers. */
70 /* "location" is special because we need to simulate "location.href"
71 * when the code is asking directly for "location". We do not register
72 * it as a "known" property since that was yielding strange bugs
73 * (SpiderMonkey was still asking us about the "location" string after
74 * assigning to it once), instead we do just a little string
76 const JSPropertySpec window_props
[] = {
77 { "closed", JSP_WIN_CLOSED
, JSPROP_ENUMERATE
| JSPROP_READONLY
},
78 { "parent", JSP_WIN_PARENT
, JSPROP_ENUMERATE
| JSPROP_READONLY
},
79 { "self", JSP_WIN_SELF
, JSPROP_ENUMERATE
| JSPROP_READONLY
},
80 { "status", JSP_WIN_STATUS
, JSPROP_ENUMERATE
},
81 { "top", JSP_WIN_TOP
, JSPROP_ENUMERATE
| JSPROP_READONLY
},
82 { "window", JSP_WIN_SELF
, JSPROP_ENUMERATE
| JSPROP_READONLY
},
88 try_resolve_frame(struct document_view
*doc_view
, unsigned char *id
)
90 struct session
*ses
= doc_view
->session
;
94 target
= ses_find_frame(ses
, id
);
95 if (!target
) return NULL
;
96 if (target
->vs
.ecmascript_fragile
)
97 ecmascript_reset_state(&target
->vs
);
98 if (!target
->vs
.ecmascript
) return NULL
;
99 return JS_GetGlobalObject(target
->vs
.ecmascript
->backend_data
);
103 static struct frame_desc
*
104 find_child_frame(struct document_view
*doc_view
, struct frame_desc
*tframe
)
106 struct frameset_desc
*frameset
= doc_view
->document
->frame_desc
;
112 for (i
= 0; i
< frameset
->n
; i
++) {
113 struct frame_desc
*frame
= &frameset
->frame_desc
[i
];
123 /* @window_class.getProperty */
125 window_get_property(JSContext
*ctx
, JSObject
*obj
, jsid id
, jsval
*vp
)
127 struct view_state
*vs
;
129 /* This can be called if @obj if not itself an instance of the
130 * appropriate class but has one in its prototype chain. Fail
132 if (!JS_InstanceOf(ctx
, obj
, (JSClass
*) &window_class
, NULL
))
135 vs
= JS_GetInstancePrivate(ctx
, obj
, (JSClass
*) &window_class
, NULL
);
137 /* No need for special window.location measurements - when
138 * location is then evaluated in string context, toString()
139 * is called which we overrode for that class below, so
140 * everything's fine. */
141 if (JSID_IS_STRING(id
)) {
142 struct document_view
*doc_view
= vs
->doc_view
;
145 obj
= try_resolve_frame(doc_view
, jsid_to_string(ctx
, &id
));
146 /* TODO: Try other lookups (mainly element lookup) until
147 * something yields data. */
149 object_to_jsval(ctx
, vp
, obj
);
154 if (!JSID_IS_INT(id
))
157 undef_to_jsval(ctx
, vp
);
159 switch (JSID_TO_INT(id
)) {
161 /* TODO: It will be a major PITA to implement this properly.
162 * Well, perhaps not so much if we introduce reference tracking
163 * for (struct session)? Still... --pasky */
164 boolean_to_jsval(ctx
, vp
, 0);
167 object_to_jsval(ctx
, vp
, obj
);
170 /* XXX: It would be nice if the following worked, yes.
171 * The problem is that we get called at the point where
172 * document.frame properties are going to be mostly NULL.
173 * But the problem is deeper because at that time we are
174 * yet building scrn_frames so our parent might not be there
175 * yet (XXX: is this true?). The true solution will be to just
176 * have struct document_view *(document_view.parent). --pasky */
177 /* FIXME: So now we alias window.parent to window.top, which is
178 * INCORRECT but works for the most common cases of just two
179 * frames. Better something than nothing. */
182 /* This is horrible. */
183 struct document_view
*doc_view
= vs
->doc_view
;
184 struct session
*ses
= doc_view
->session
;
185 struct frame_desc
*frame
= doc_view
->document
->frame
;
187 if (!ses
->doc_view
->document
->frame_desc
) {
188 INTERNAL("Looking for parent but there're no frames.");
192 doc_view
= ses
->doc_view
;
193 if (find_child_frame(doc_view
, frame
))
195 foreach (doc_view
, ses
->scrn_frames
) {
196 if (find_child_frame(doc_view
, frame
))
199 INTERNAL("Cannot find frame %s parent.",doc_view
->name
);
203 some_domain_security_check();
204 if (doc_view
->vs
.ecmascript_fragile
)
205 ecmascript_reset_state(&doc_view
->vs
);
206 assert(doc_view
->ecmascript
);
207 object_to_jsval(ctx
, vp
, JS_GetGlobalObject(doc_view
->ecmascript
->backend_data
));
215 struct document_view
*doc_view
= vs
->doc_view
;
216 struct document_view
*top_view
= doc_view
->session
->doc_view
;
217 JSObject
*newjsframe
;
219 assert(top_view
&& top_view
->vs
);
220 if (top_view
->vs
->ecmascript_fragile
)
221 ecmascript_reset_state(top_view
->vs
);
222 if (!top_view
->vs
->ecmascript
)
224 newjsframe
= JS_GetGlobalObject(top_view
->vs
->ecmascript
->backend_data
);
226 /* Keep this unrolled this way. Will have to check document.domain
228 /* Note that this check is perhaps overparanoid. If top windows
229 * is alien but some other child window is not, we should still
230 * let the script walk thru. That'd mean moving the check to
231 * other individual properties in this switch. */
232 if (compare_uri(vs
->uri
, top_view
->vs
->uri
, URI_HOST
))
233 object_to_jsval(ctx
, vp
, newjsframe
);
235 /****X*X*X*** SECURITY VIOLATION! RED ALERT, SHIELDS UP! ***X*X*X****\
236 |* (Pasky was apparently looking at the Links2 JS code . ___ ^.^ *|
237 \* for too long.) `.(,_,)\o/ */
241 /* Unrecognized integer property ID; someone is using
242 * the object as an array. SMJS builtin classes (e.g.
243 * js_RegExpClass) just return JS_TRUE in this case
244 * and leave *@vp unchanged. Do the same here.
245 * (Actually not quite the same, as we already used
246 * @undef_to_jsval.) */
253 void location_goto(struct document_view
*doc_view
, unsigned char *url
);
255 /* @window_class.setProperty */
257 window_set_property(JSContext
*ctx
, JSObject
*obj
, jsid id
, JSBool strict
, jsval
*vp
)
259 struct view_state
*vs
;
261 /* This can be called if @obj if not itself an instance of the
262 * appropriate class but has one in its prototype chain. Fail
264 if (!JS_InstanceOf(ctx
, obj
, (JSClass
*) &window_class
, NULL
))
267 vs
= JS_GetInstancePrivate(ctx
, obj
, (JSClass
*) &window_class
, NULL
);
269 if (JSID_IS_STRING(id
)) {
270 if (!strcmp(jsid_to_string(ctx
, &id
), "location")) {
271 struct document_view
*doc_view
= vs
->doc_view
;
273 location_goto(doc_view
, jsval_to_string(ctx
, vp
));
274 /* Do NOT touch our .location property, evil
281 if (!JSID_IS_INT(id
))
284 switch (JSID_TO_INT(id
)) {
286 mem_free_set(&vs
->doc_view
->session
->status
.window_status
, stracpy(jsval_to_string(ctx
, vp
)));
287 print_screen_status(vs
->doc_view
->session
);
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 * Do the same here. */
301 static JSBool
window_alert(JSContext
*ctx
, uintN argc
, jsval
*rval
);
302 static JSBool
window_open(JSContext
*ctx
, uintN argc
, jsval
*rval
);
303 static JSBool
window_setTimeout(JSContext
*ctx
, uintN argc
, jsval
*rval
);
305 const spidermonkeyFunctionSpec window_funcs
[] = {
306 { "alert", window_alert
, 1 },
307 { "open", window_open
, 3 },
308 { "setTimeout", window_setTimeout
, 2 },
312 /* @window_funcs{"alert"} */
314 window_alert(JSContext
*ctx
, uintN argc
, jsval
*rval
)
317 JSObject
*obj
= JS_THIS_OBJECT(ctx
, rval
);
318 jsval
*argv
= JS_ARGV(ctx
, rval
);
319 struct view_state
*vs
;
320 unsigned char *string
;
322 if (!JS_InstanceOf(ctx
, obj
, (JSClass
*) &window_class
, argv
)) return JS_FALSE
;
324 vs
= JS_GetInstancePrivate(ctx
, obj
, (JSClass
*) &window_class
, argv
);
329 string
= jsval_to_string(ctx
, &argv
[0]);
333 info_box(vs
->doc_view
->session
->tab
->term
, MSGBOX_FREE_TEXT
,
334 N_("JavaScript Alert"), ALIGN_CENTER
, stracpy(string
));
336 undef_to_jsval(ctx
, &val
);
337 JS_SET_RVAL(ctx
, rval
, val
);
341 /* @window_funcs{"open"} */
343 window_open(JSContext
*ctx
, uintN argc
, jsval
*rval
)
346 JSObject
*obj
= JS_THIS_OBJECT(ctx
, rval
);
347 jsval
*argv
= JS_ARGV(ctx
, rval
);
348 struct view_state
*vs
;
349 struct document_view
*doc_view
;
351 unsigned char *frame
= NULL
;
352 unsigned char *url
, *url2
;
354 static time_t ratelimit_start
;
355 static int ratelimit_count
;
357 if (!JS_InstanceOf(ctx
, obj
, (JSClass
*) &window_class
, argv
)) return JS_FALSE
;
359 vs
= JS_GetInstancePrivate(ctx
, obj
, (JSClass
*) &window_class
, argv
);
360 doc_view
= vs
->doc_view
;
361 ses
= doc_view
->session
;
363 if (get_opt_bool("ecmascript.block_window_opening", ses
)) {
365 set_led_value(ses
->status
.popup_led
, 'P');
370 if (argc
< 1) return JS_TRUE
;
372 /* Ratelimit window opening. Recursive window.open() is very nice.
373 * We permit at most 20 tabs in 2 seconds. The ratelimiter is very
374 * rough but shall suffice against the usual cases. */
375 if (!ratelimit_start
|| time(NULL
) - ratelimit_start
> 2) {
376 ratelimit_start
= time(NULL
);
380 if (ratelimit_count
> 20) {
385 url
= stracpy(jsval_to_string(ctx
, &argv
[0]));
386 trim_chars(url
, ' ', 0);
387 url2
= join_urls(doc_view
->document
->uri
, url
);
393 frame
= stracpy(jsval_to_string(ctx
, &argv
[1]));
400 /* TODO: Support for window naming and perhaps some window features? */
402 uri
= get_uri(url2
, 0);
409 if (frame
&& *frame
&& c_strcasecmp(frame
, "_blank")) {
410 struct delayed_open
*deo
= mem_calloc(1, sizeof(*deo
));
414 deo
->uri
= get_uri_reference(uri
);
415 deo
->target
= stracpy(frame
);
416 register_bottom_half(delayed_goto_uri_frame
, deo
);
417 boolean_to_jsval(ctx
, &val
, 1);
422 if (!get_cmd_opt_bool("no-connect")
423 && !get_cmd_opt_bool("no-home")
424 && !get_cmd_opt_bool("anonymous")
425 && can_open_in_new(ses
->tab
->term
)) {
426 open_uri_in_new_window(ses
, uri
, NULL
, ENV_ANY
,
427 CACHE_MODE_NORMAL
, TASK_NONE
);
428 boolean_to_jsval(ctx
, &val
, 1);
430 /* When opening a new tab, we might get rerendered, losing our
431 * context and triggerring a disaster, so postpone that. */
432 struct delayed_open
*deo
= mem_calloc(1, sizeof(*deo
));
436 deo
->uri
= get_uri_reference(uri
);
437 register_bottom_half(delayed_open
, deo
);
438 boolean_to_jsval(ctx
, &val
, 1);
440 undef_to_jsval(ctx
, &val
);
448 JS_SET_RVAL(ctx
, rval
, val
);
452 /* @window_funcs{"setTimeout"} */
454 window_setTimeout(JSContext
*ctx
, uintN argc
, jsval
*rval
)
456 jsval
*argv
= JS_ARGV(ctx
, rval
);
457 struct ecmascript_interpreter
*interpreter
= JS_GetContextPrivate(ctx
);
464 code
= jsval_to_string(ctx
, &argv
[0]);
468 code
= stracpy(code
);
471 timeout
= atoi(jsval_to_string(ctx
, &argv
[1]));
476 ecmascript_set_timeout(interpreter
, code
, timeout
);