SMJS: Use JS_GetInstancePrivate where applicable.
[elinks.git] / src / ecmascript / spidermonkey / window.c
blob8ece2e167df42c958c5a7bfe3becd865c26fdc93
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, jsval id, jsval *vp);
48 static JSBool window_set_property(JSContext *ctx, JSObject *obj, jsval id, jsval *vp);
50 const JSClass window_class = {
51 "window",
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
59 enum window_prop {
60 JSP_WIN_CLOSED,
61 JSP_WIN_PARENT,
62 JSP_WIN_SELF,
63 JSP_WIN_TOP,
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
70 * comparing. */
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 },
77 { NULL }
81 static JSObject *
82 try_resolve_frame(struct document_view *doc_view, unsigned char *id)
84 struct session *ses = doc_view->session;
85 struct frame *target;
87 assert(ses);
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);
96 #if 0
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;
101 int i;
103 if (!frameset)
104 return NULL;
106 for (i = 0; i < frameset->n; i++) {
107 struct frame_desc *frame = &frameset->frame_desc[i];
109 if (frame == tframe)
110 return frame;
113 return NULL;
115 #endif
117 /* @window_class.getProperty */
118 static JSBool
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
125 * such calls. */
126 if (!JS_InstanceOf(ctx, obj, (JSClass *) &window_class, NULL))
127 return JS_FALSE;
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;
137 JSObject *obj;
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. */
142 if (obj) {
143 object_to_jsval(ctx, vp, obj);
145 return JS_TRUE;
148 if (!JSVAL_IS_INT(id))
149 return JS_TRUE;
151 undef_to_jsval(ctx, vp);
153 switch (JSVAL_TO_INT(id)) {
154 case JSP_WIN_CLOSED:
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);
159 break;
160 case JSP_WIN_SELF:
161 object_to_jsval(ctx, vp, obj);
162 break;
163 case JSP_WIN_PARENT:
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. */
174 #if 0
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.");
183 break;
185 assert(frame);
186 doc_view = ses->doc_view;
187 if (find_child_frame(doc_view, frame))
188 goto found_parent;
189 foreach (doc_view, ses->scrn_frames) {
190 if (find_child_frame(doc_view, frame))
191 goto found_parent;
193 INTERNAL("Cannot find frame %s parent.",doc_view->name);
194 break;
196 found_parent:
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));
202 break;
204 #endif
205 case JSP_WIN_TOP:
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)
215 break;
216 newjsframe = JS_GetGlobalObject(top_view->vs->ecmascript->backend_data);
218 /* Keep this unrolled this way. Will have to check document.domain
219 * JS property. */
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);
226 /* else */
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/ */
230 break;
232 default:
233 /* Unrecognized property ID; someone is using the
234 * 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.) */
239 break;
242 return JS_TRUE;
245 void location_goto(struct document_view *doc_view, unsigned char *url);
247 /* @window_class.setProperty */
248 static JSBool
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
255 * such calls. */
256 if (!JS_InstanceOf(ctx, obj, (JSClass *) &window_class, NULL))
257 return JS_FALSE;
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
267 * SpiderMonkey!! */
268 return JS_FALSE;
270 return JS_TRUE;
273 if (!JSVAL_IS_INT(id))
274 return JS_TRUE;
276 switch (JSVAL_TO_INT(id)) {
277 default:
278 /* Unrecognized property ID; someone is using the
279 * object as an array. SMJS builtin classes (e.g.
280 * js_RegExpClass) just return JS_TRUE in this case.
281 * Do the same here. */
282 return JS_TRUE;
285 return JS_TRUE;
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 JSFunctionSpec window_funcs[] = {
293 { "alert", window_alert, 1 },
294 { "open", window_open, 3 },
295 { NULL }
298 /* @window_funcs{"alert"} */
299 static JSBool
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);
309 if (argc != 1)
310 return JS_TRUE;
312 string = jsval_to_string(ctx, &argv[0]);
313 if (!*string)
314 return JS_TRUE;
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);
320 return JS_TRUE;
323 struct delayed_open {
324 struct session *ses;
325 struct uri *uri;
326 unsigned char *target;
329 static void
330 delayed_open(void *data)
332 struct delayed_open *deo = data;
334 assert(deo);
335 open_uri_in_new_tab(deo->ses, deo->uri, 0, 0);
336 done_uri(deo->uri);
337 mem_free(deo);
340 static void
341 delayed_goto_uri_frame(void *data)
343 struct delayed_open *deo = data;
345 assert(deo);
346 goto_uri_frame(deo->ses, deo->uri, deo->target, CACHE_MODE_NORMAL);
347 done_uri(deo->uri);
348 mem_free(deo);
351 /* @window_funcs{"open"} */
352 static JSBool
353 window_open(JSContext *ctx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
355 struct view_state *vs;
356 struct document_view *doc_view;
357 struct session *ses;
358 unsigned char *target = "";
359 unsigned char *url;
360 struct uri *uri;
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")) {
371 #ifdef CONFIG_LEDS
372 set_led_value(ses->status.popup_led, 'P');
373 #endif
374 return JS_TRUE;
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);
385 ratelimit_count = 0;
386 } else {
387 ratelimit_count++;
388 if (ratelimit_count > 20)
389 return JS_TRUE;
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);
400 mem_free(url);
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));
408 if (deo) {
409 deo->ses = ses;
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);
414 goto end;
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);
425 } else {
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));
430 if (deo) {
431 deo->ses = ses;
432 deo->uri = get_uri_reference(uri);
433 register_bottom_half(delayed_open, deo);
434 boolean_to_jsval(ctx, rval, 1);
435 } else {
436 undef_to_jsval(ctx, rval);
440 end:
441 done_uri(uri);
443 return JS_TRUE;