iconv: Bail out of the loop when an illegal sequence of bytes occurs.
[elinks/elinks-j605.git] / src / ecmascript / spidermonkey / window.c
blob93cba2b841ffda8bd742f1ecd002f03121cb9eef
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/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 = {
51 "window",
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. */
63 enum window_prop {
64 JSP_WIN_CLOSED = -1,
65 JSP_WIN_PARENT = -2,
66 JSP_WIN_SELF = -3,
67 JSP_WIN_STATUS = -4,
68 JSP_WIN_TOP = -5,
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
75 * comparing. */
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 },
83 { NULL }
87 static JSObject *
88 try_resolve_frame(struct document_view *doc_view, unsigned char *id)
90 struct session *ses = doc_view->session;
91 struct frame *target;
93 assert(ses);
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);
102 #if 0
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;
107 int i;
109 if (!frameset)
110 return NULL;
112 for (i = 0; i < frameset->n; i++) {
113 struct frame_desc *frame = &frameset->frame_desc[i];
115 if (frame == tframe)
116 return frame;
119 return NULL;
121 #endif
123 /* @window_class.getProperty */
124 static JSBool
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
131 * such calls. */
132 if (!JS_InstanceOf(ctx, obj, (JSClass *) &window_class, NULL))
133 return JS_FALSE;
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;
143 JSObject *obj;
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. */
148 if (obj) {
149 object_to_jsval(ctx, vp, obj);
151 return JS_TRUE;
154 if (!JSID_IS_INT(id))
155 return JS_TRUE;
157 undef_to_jsval(ctx, vp);
159 switch (JSID_TO_INT(id)) {
160 case JSP_WIN_CLOSED:
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);
165 break;
166 case JSP_WIN_SELF:
167 object_to_jsval(ctx, vp, obj);
168 break;
169 case JSP_WIN_PARENT:
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. */
180 #if 0
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.");
189 break;
191 assert(frame);
192 doc_view = ses->doc_view;
193 if (find_child_frame(doc_view, frame))
194 goto found_parent;
195 foreach (doc_view, ses->scrn_frames) {
196 if (find_child_frame(doc_view, frame))
197 goto found_parent;
199 INTERNAL("Cannot find frame %s parent.",doc_view->name);
200 break;
202 found_parent:
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));
208 break;
210 #endif
211 case JSP_WIN_STATUS:
212 return JS_FALSE;
213 case JSP_WIN_TOP:
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)
223 break;
224 newjsframe = JS_GetGlobalObject(top_view->vs->ecmascript->backend_data);
226 /* Keep this unrolled this way. Will have to check document.domain
227 * JS property. */
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);
234 /* else */
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/ */
238 break;
240 default:
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.) */
247 break;
250 return JS_TRUE;
253 void location_goto(struct document_view *doc_view, unsigned char *url);
255 /* @window_class.setProperty */
256 static JSBool
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
263 * such calls. */
264 if (!JS_InstanceOf(ctx, obj, (JSClass *) &window_class, NULL))
265 return JS_FALSE;
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
275 * SpiderMonkey!! */
276 return JS_FALSE;
278 return JS_TRUE;
281 if (!JSID_IS_INT(id))
282 return JS_TRUE;
284 switch (JSID_TO_INT(id)) {
285 case JSP_WIN_STATUS:
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);
288 return JS_TRUE;
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 * Do the same here. */
294 return JS_TRUE;
297 return JS_TRUE;
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 },
309 { NULL }
312 /* @window_funcs{"alert"} */
313 static JSBool
314 window_alert(JSContext *ctx, uintN argc, jsval *rval)
316 jsval val;
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);
326 if (argc != 1)
327 return JS_TRUE;
329 string = jsval_to_string(ctx, &argv[0]);
330 if (!*string)
331 return JS_TRUE;
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);
338 return JS_TRUE;
341 /* @window_funcs{"open"} */
342 static JSBool
343 window_open(JSContext *ctx, uintN argc, jsval *rval)
345 jsval val;
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;
350 struct session *ses;
351 unsigned char *frame = NULL;
352 unsigned char *url, *url2;
353 struct uri *uri;
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)) {
364 #ifdef CONFIG_LEDS
365 set_led_value(ses->status.popup_led, 'P');
366 #endif
367 return JS_TRUE;
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);
377 ratelimit_count = 0;
378 } else {
379 ratelimit_count++;
380 if (ratelimit_count > 20) {
381 return JS_TRUE;
385 url = stracpy(jsval_to_string(ctx, &argv[0]));
386 trim_chars(url, ' ', 0);
387 url2 = join_urls(doc_view->document->uri, url);
388 mem_free(url);
389 if (!url2) {
390 return JS_TRUE;
392 if (argc > 1) {
393 frame = stracpy(jsval_to_string(ctx, &argv[1]));
394 if (!frame) {
395 mem_free(url2);
396 return JS_TRUE;
400 /* TODO: Support for window naming and perhaps some window features? */
402 uri = get_uri(url2, 0);
403 mem_free(url2);
404 if (!uri) {
405 mem_free_if(frame);
406 return JS_TRUE;
409 if (frame && *frame && c_strcasecmp(frame, "_blank")) {
410 struct delayed_open *deo = mem_calloc(1, sizeof(*deo));
412 if (deo) {
413 deo->ses = ses;
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);
418 goto end;
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);
429 } else {
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));
434 if (deo) {
435 deo->ses = ses;
436 deo->uri = get_uri_reference(uri);
437 register_bottom_half(delayed_open, deo);
438 boolean_to_jsval(ctx, &val, 1);
439 } else {
440 undef_to_jsval(ctx, &val);
444 end:
445 done_uri(uri);
446 mem_free_if(frame);
448 JS_SET_RVAL(ctx, rval, val);
449 return JS_TRUE;
452 /* @window_funcs{"setTimeout"} */
453 static JSBool
454 window_setTimeout(JSContext *ctx, uintN argc, jsval *rval)
456 jsval *argv = JS_ARGV(ctx, rval);
457 struct ecmascript_interpreter *interpreter = JS_GetContextPrivate(ctx);
458 unsigned char *code;
459 int timeout;
461 if (argc != 2)
462 return JS_TRUE;
464 code = jsval_to_string(ctx, &argv[0]);
465 if (!*code)
466 return JS_TRUE;
468 code = stracpy(code);
469 if (!code)
470 return JS_TRUE;
471 timeout = atoi(jsval_to_string(ctx, &argv[1]));
472 if (timeout <= 0) {
473 mem_free(code);
474 return JS_TRUE;
476 ecmascript_set_timeout(interpreter, code, timeout);
477 return JS_TRUE;