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
, jsval id
, jsval
*vp
);
48 static JSBool
window_set_property(JSContext
*ctx
, JSObject
*obj
, jsval id
, jsval
*vp
);
50 const JSClass window_class
= {
52 JSCLASS_HAS_PRIVATE
, /* struct view_state * */
53 JS_PropertyStub
, JS_PropertyStub
,
54 window_get_property
, window_set_property
,
55 JS_EnumerateStub
, JS_ResolveStub
, JS_ConvertStub
, JS_FinalizeStub
65 /* "location" is special because we need to simulate "location.href"
66 * when the code is asking directly for "location". We do not register
67 * it as a "known" property since that was yielding strange bugs
68 * (SpiderMonkey was still asking us about the "location" string after
69 * assigning to it once), instead we do just a little string
71 const JSPropertySpec window_props
[] = {
72 { "closed", JSP_WIN_CLOSED
, JSPROP_ENUMERATE
| JSPROP_READONLY
},
73 { "parent", JSP_WIN_PARENT
, JSPROP_ENUMERATE
| JSPROP_READONLY
},
74 { "self", JSP_WIN_SELF
, JSPROP_ENUMERATE
| JSPROP_READONLY
},
75 { "top", JSP_WIN_TOP
, JSPROP_ENUMERATE
| JSPROP_READONLY
},
76 { "window", JSP_WIN_SELF
, JSPROP_ENUMERATE
| JSPROP_READONLY
},
82 try_resolve_frame(struct document_view
*doc_view
, unsigned char *id
)
84 struct session
*ses
= doc_view
->session
;
88 target
= ses_find_frame(ses
, id
);
89 if (!target
) return NULL
;
90 if (target
->vs
.ecmascript_fragile
)
91 ecmascript_reset_state(&target
->vs
);
92 if (!target
->vs
.ecmascript
) return NULL
;
93 return JS_GetGlobalObject(target
->vs
.ecmascript
->backend_data
);
97 static struct frame_desc
*
98 find_child_frame(struct document_view
*doc_view
, struct frame_desc
*tframe
)
100 struct frameset_desc
*frameset
= doc_view
->document
->frame_desc
;
106 for (i
= 0; i
< frameset
->n
; i
++) {
107 struct frame_desc
*frame
= &frameset
->frame_desc
[i
];
117 /* @window_class.getProperty */
119 window_get_property(JSContext
*ctx
, JSObject
*obj
, jsval id
, jsval
*vp
)
121 struct view_state
*vs
;
123 /* This can be called if @obj if not itself an instance of the
124 * appropriate class but has one in its prototype chain. Fail
126 if (!JS_InstanceOf(ctx
, obj
, (JSClass
*) &window_class
, NULL
))
129 vs
= JS_GetInstancePrivate(ctx
, obj
, (JSClass
*) &window_class
, NULL
);
131 /* No need for special window.location measurements - when
132 * location is then evaluated in string context, toString()
133 * is called which we overrode for that class below, so
134 * everything's fine. */
135 if (JSVAL_IS_STRING(id
)) {
136 struct document_view
*doc_view
= vs
->doc_view
;
139 obj
= try_resolve_frame(doc_view
, jsval_to_string(ctx
, &id
));
140 /* TODO: Try other lookups (mainly element lookup) until
141 * something yields data. */
143 object_to_jsval(ctx
, vp
, obj
);
148 if (!JSVAL_IS_INT(id
))
151 undef_to_jsval(ctx
, vp
);
153 switch (JSVAL_TO_INT(id
)) {
155 /* TODO: It will be a major PITA to implement this properly.
156 * Well, perhaps not so much if we introduce reference tracking
157 * for (struct session)? Still... --pasky */
158 boolean_to_jsval(ctx
, vp
, 0);
161 object_to_jsval(ctx
, vp
, obj
);
164 /* XXX: It would be nice if the following worked, yes.
165 * The problem is that we get called at the point where
166 * document.frame properties are going to be mostly NULL.
167 * But the problem is deeper because at that time we are
168 * yet building scrn_frames so our parent might not be there
169 * yet (XXX: is this true?). The true solution will be to just
170 * have struct document_view *(document_view.parent). --pasky */
171 /* FIXME: So now we alias window.parent to window.top, which is
172 * INCORRECT but works for the most common cases of just two
173 * frames. Better something than nothing. */
176 /* This is horrible. */
177 struct document_view
*doc_view
= vs
->doc_view
;
178 struct session
*ses
= doc_view
->session
;
179 struct frame_desc
*frame
= doc_view
->document
->frame
;
181 if (!ses
->doc_view
->document
->frame_desc
) {
182 INTERNAL("Looking for parent but there're no frames.");
186 doc_view
= ses
->doc_view
;
187 if (find_child_frame(doc_view
, frame
))
189 foreach (doc_view
, ses
->scrn_frames
) {
190 if (find_child_frame(doc_view
, frame
))
193 INTERNAL("Cannot find frame %s parent.",doc_view
->name
);
197 some_domain_security_check();
198 if (doc_view
->vs
.ecmascript_fragile
)
199 ecmascript_reset_state(&doc_view
->vs
);
200 assert(doc_view
->ecmascript
);
201 object_to_jsval(ctx
, vp
, JS_GetGlobalObject(doc_view
->ecmascript
->backend_data
));
207 struct document_view
*doc_view
= vs
->doc_view
;
208 struct document_view
*top_view
= doc_view
->session
->doc_view
;
209 JSObject
*newjsframe
;
211 assert(top_view
&& top_view
->vs
);
212 if (top_view
->vs
->ecmascript_fragile
)
213 ecmascript_reset_state(top_view
->vs
);
214 if (!top_view
->vs
->ecmascript
)
216 newjsframe
= JS_GetGlobalObject(top_view
->vs
->ecmascript
->backend_data
);
218 /* Keep this unrolled this way. Will have to check document.domain
220 /* Note that this check is perhaps overparanoid. If top windows
221 * is alien but some other child window is not, we should still
222 * let the script walk thru. That'd mean moving the check to
223 * other individual properties in this switch. */
224 if (compare_uri(vs
->uri
, top_view
->vs
->uri
, URI_HOST
))
225 object_to_jsval(ctx
, vp
, newjsframe
);
227 /****X*X*X*** SECURITY VIOLATION! RED ALERT, SHIELDS UP! ***X*X*X****\
228 |* (Pasky was apparently looking at the Links2 JS code . ___ ^.^ *|
229 \* for too long.) `.(,_,)\o/ */
233 /* Unrecognized integer property ID; someone is using
234 * the object as an array. SMJS builtin classes (e.g.
235 * js_RegExpClass) just return JS_TRUE in this case
236 * and leave *@vp unchanged. Do the same here.
237 * (Actually not quite the same, as we already used
238 * @undef_to_jsval.) */
245 void location_goto(struct document_view
*doc_view
, unsigned char *url
);
247 /* @window_class.setProperty */
249 window_set_property(JSContext
*ctx
, JSObject
*obj
, jsval id
, jsval
*vp
)
251 struct view_state
*vs
;
253 /* This can be called if @obj if not itself an instance of the
254 * appropriate class but has one in its prototype chain. Fail
256 if (!JS_InstanceOf(ctx
, obj
, (JSClass
*) &window_class
, NULL
))
259 vs
= JS_GetInstancePrivate(ctx
, obj
, (JSClass
*) &window_class
, NULL
);
261 if (JSVAL_IS_STRING(id
)) {
262 if (!strcmp(jsval_to_string(ctx
, &id
), "location")) {
263 struct document_view
*doc_view
= vs
->doc_view
;
265 location_goto(doc_view
, jsval_to_string(ctx
, vp
));
266 /* Do NOT touch our .location property, evil
273 if (!JSVAL_IS_INT(id
))
276 switch (JSVAL_TO_INT(id
)) {
278 /* Unrecognized integer property ID; someone is using
279 * the object as an array. SMJS builtin classes (e.g.
280 * js_RegExpClass) just return JS_TRUE in this case.
281 * Do the same here. */
289 static JSBool
window_alert(JSContext
*ctx
, JSObject
*obj
, uintN argc
, jsval
*argv
, jsval
*rval
);
290 static JSBool
window_open(JSContext
*ctx
, JSObject
*obj
, uintN argc
, jsval
*argv
, jsval
*rval
);
292 const spidermonkeyFunctionSpec window_funcs
[] = {
293 { "alert", window_alert
, 1 },
294 { "open", window_open
, 3 },
298 /* @window_funcs{"alert"} */
300 window_alert(JSContext
*ctx
, JSObject
*obj
, uintN argc
, jsval
*argv
, jsval
*rval
)
302 struct view_state
*vs
;
303 unsigned char *string
;
305 if (!JS_InstanceOf(ctx
, obj
, (JSClass
*) &window_class
, argv
)) return JS_FALSE
;
307 vs
= JS_GetInstancePrivate(ctx
, obj
, (JSClass
*) &window_class
, argv
);
312 string
= jsval_to_string(ctx
, &argv
[0]);
316 info_box(vs
->doc_view
->session
->tab
->term
, MSGBOX_FREE_TEXT
,
317 N_("JavaScript Alert"), ALIGN_CENTER
, stracpy(string
));
319 undef_to_jsval(ctx
, rval
);
323 struct delayed_open
{
326 unsigned char *target
;
330 delayed_open(void *data
)
332 struct delayed_open
*deo
= data
;
335 open_uri_in_new_tab(deo
->ses
, deo
->uri
, 0, 0);
341 delayed_goto_uri_frame(void *data
)
343 struct delayed_open
*deo
= data
;
346 goto_uri_frame(deo
->ses
, deo
->uri
, deo
->target
, CACHE_MODE_NORMAL
);
351 /* @window_funcs{"open"} */
353 window_open(JSContext
*ctx
, JSObject
*obj
, uintN argc
, jsval
*argv
, jsval
*rval
)
355 struct view_state
*vs
;
356 struct document_view
*doc_view
;
358 unsigned char *target
= "";
361 static time_t ratelimit_start
;
362 static int ratelimit_count
;
364 if (!JS_InstanceOf(ctx
, obj
, (JSClass
*) &window_class
, argv
)) return JS_FALSE
;
366 vs
= JS_GetInstancePrivate(ctx
, obj
, (JSClass
*) &window_class
, argv
);
367 doc_view
= vs
->doc_view
;
368 ses
= doc_view
->session
;
370 if (get_opt_bool("ecmascript.block_window_opening")) {
372 set_led_value(ses
->status
.popup_led
, 'P');
377 if (argc
< 1) return JS_TRUE
;
379 /* Ratelimit window opening. Recursive window.open() is very nice.
380 * We permit at most 20 tabs in 2 seconds. The ratelimiter is very
381 * rough but shall suffice against the usual cases. */
383 if (!ratelimit_start
|| time(NULL
) - ratelimit_start
> 2) {
384 ratelimit_start
= time(NULL
);
388 if (ratelimit_count
> 20)
392 url
= jsval_to_string(ctx
, &argv
[0]);
394 /* TODO: Support for window naming and perhaps some window features? */
396 url
= join_urls(doc_view
->document
->uri
,
397 trim_chars(url
, ' ', 0));
398 if (!url
) return JS_TRUE
;
399 uri
= get_uri(url
, 0);
401 if (!uri
) return JS_TRUE
;
403 if (argc
> 1) target
= jsval_to_string(ctx
, &argv
[1]);
405 if (*target
&& strcasecmp(target
, "_blank")) {
406 struct delayed_open
*deo
= mem_calloc(1, sizeof(*deo
));
410 deo
->uri
= get_uri_reference(uri
);
411 deo
->target
= target
;
412 register_bottom_half(delayed_goto_uri_frame
, deo
);
413 boolean_to_jsval(ctx
, rval
, 1);
418 if (!get_cmd_opt_bool("no-connect")
419 && !get_cmd_opt_bool("no-home")
420 && !get_cmd_opt_bool("anonymous")
421 && can_open_in_new(ses
->tab
->term
)) {
422 open_uri_in_new_window(ses
, uri
, NULL
, ENV_ANY
,
423 CACHE_MODE_NORMAL
, TASK_NONE
);
424 boolean_to_jsval(ctx
, rval
, 1);
426 /* When opening a new tab, we might get rerendered, losing our
427 * context and triggerring a disaster, so postpone that. */
428 struct delayed_open
*deo
= mem_calloc(1, sizeof(*deo
));
432 deo
->uri
= get_uri_reference(uri
);
433 register_bottom_half(delayed_open
, deo
);
434 boolean_to_jsval(ctx
, rval
, 1);
436 undef_to_jsval(ctx
, rval
);