SpiderMonkey: do not remember context. Maybe this time it won't hung.
[elinks.git] / src / ecmascript / spidermonkey / window.c
blob214bfc9d4619e04d5516137de75613be64cb339f
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,
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 static JSBool
118 window_get_property(JSContext *ctx, JSObject *obj, jsval id, jsval *vp)
120 struct view_state *vs = JS_GetPrivate(ctx, obj);
122 /* No need for special window.location measurements - when
123 * location is then evaluated in string context, toString()
124 * is called which we overrode for that class below, so
125 * everything's fine. */
126 if (JSVAL_IS_STRING(id)) {
127 struct document_view *doc_view = vs->doc_view;
128 JSObject *obj;
130 obj = try_resolve_frame(doc_view, jsval_to_string(ctx, &id));
131 /* TODO: Try other lookups (mainly element lookup) until
132 * something yields data. */
133 if (obj) {
134 object_to_jsval(ctx, vp, obj);
136 return JS_TRUE;
139 if (!JSVAL_IS_INT(id))
140 return JS_TRUE;
142 undef_to_jsval(ctx, vp);
144 switch (JSVAL_TO_INT(id)) {
145 case JSP_WIN_CLOSED:
146 /* TODO: It will be a major PITA to implement this properly.
147 * Well, perhaps not so much if we introduce reference tracking
148 * for (struct session)? Still... --pasky */
149 boolean_to_jsval(ctx, vp, 0);
150 break;
151 case JSP_WIN_SELF:
152 object_to_jsval(ctx, vp, obj);
153 break;
154 case JSP_WIN_PARENT:
155 /* XXX: It would be nice if the following worked, yes.
156 * The problem is that we get called at the point where
157 * document.frame properties are going to be mostly NULL.
158 * But the problem is deeper because at that time we are
159 * yet building scrn_frames so our parent might not be there
160 * yet (XXX: is this true?). The true solution will be to just
161 * have struct document_view *(document_view.parent). --pasky */
162 /* FIXME: So now we alias window.parent to window.top, which is
163 * INCORRECT but works for the most common cases of just two
164 * frames. Better something than nothing. */
165 #if 0
167 /* This is horrible. */
168 struct document_view *doc_view = vs->doc_view;
169 struct session *ses = doc_view->session;
170 struct frame_desc *frame = doc_view->document->frame;
172 if (!ses->doc_view->document->frame_desc) {
173 INTERNAL("Looking for parent but there're no frames.");
174 break;
176 assert(frame);
177 doc_view = ses->doc_view;
178 if (find_child_frame(doc_view, frame))
179 goto found_parent;
180 foreach (doc_view, ses->scrn_frames) {
181 if (find_child_frame(doc_view, frame))
182 goto found_parent;
184 INTERNAL("Cannot find frame %s parent.",doc_view->name);
185 break;
187 found_parent:
188 some_domain_security_check();
189 if (doc_view->vs.ecmascript_fragile)
190 ecmascript_reset_state(&doc_view->vs);
191 assert(doc_view->ecmascript);
192 object_to_jsval(ctx, vp, JS_GetGlobalObject(doc_view->ecmascript->backend_data));
193 break;
195 #endif
196 case JSP_WIN_TOP:
198 struct document_view *doc_view = vs->doc_view;
199 struct document_view *top_view = doc_view->session->doc_view;
200 JSObject *newjsframe;
202 assert(top_view && top_view->vs);
203 if (top_view->vs->ecmascript_fragile)
204 ecmascript_reset_state(top_view->vs);
205 if (!top_view->vs->ecmascript)
206 break;
207 newjsframe = JS_GetGlobalObject(top_view->vs->ecmascript->backend_data);
209 /* Keep this unrolled this way. Will have to check document.domain
210 * JS property. */
211 /* Note that this check is perhaps overparanoid. If top windows
212 * is alien but some other child window is not, we should still
213 * let the script walk thru. That'd mean moving the check to
214 * other individual properties in this switch. */
215 if (compare_uri(vs->uri, top_view->vs->uri, URI_HOST))
216 object_to_jsval(ctx, vp, newjsframe);
217 /* else */
218 /****X*X*X*** SECURITY VIOLATION! RED ALERT, SHIELDS UP! ***X*X*X****\
219 |* (Pasky was apparently looking at the Links2 JS code . ___ ^.^ *|
220 \* for too long.) `.(,_,)\o/ */
221 break;
223 default:
224 INTERNAL("Invalid ID %d in window_get_property().", JSVAL_TO_INT(id));
225 break;
228 return JS_TRUE;
231 void location_goto(struct document_view *doc_view, unsigned char *url);
233 static JSBool
234 window_set_property(JSContext *ctx, JSObject *obj, jsval id, jsval *vp)
236 struct view_state *vs = JS_GetPrivate(ctx, obj);
238 if (JSVAL_IS_STRING(id)) {
239 if (!strcmp(jsval_to_string(ctx, &id), "location")) {
240 struct document_view *doc_view = vs->doc_view;
242 location_goto(doc_view, jsval_to_string(ctx, vp));
243 /* Do NOT touch our .location property, evil
244 * SpiderMonkey!! */
245 return JS_FALSE;
247 return JS_TRUE;
250 if (!JSVAL_IS_INT(id))
251 return JS_TRUE;
253 switch (JSVAL_TO_INT(id)) {
254 default:
255 INTERNAL("Invalid ID %d in window_set_property().", JSVAL_TO_INT(id));
256 return JS_TRUE;
259 return JS_TRUE;
263 static JSBool window_alert(JSContext *ctx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
264 static JSBool window_open(JSContext *ctx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
266 const JSFunctionSpec window_funcs[] = {
267 { "alert", window_alert, 1 },
268 { "open", window_open, 3 },
269 { NULL }
272 static JSBool
273 window_alert(JSContext *ctx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
275 struct view_state *vs = JS_GetPrivate(ctx, obj);
276 unsigned char *string;
278 if (argc != 1)
279 return JS_TRUE;
281 string = jsval_to_string(ctx, &argv[0]);
282 if (!*string)
283 return JS_TRUE;
285 info_box(vs->doc_view->session->tab->term, MSGBOX_FREE_TEXT,
286 N_("JavaScript Alert"), ALIGN_CENTER, stracpy(string));
288 undef_to_jsval(ctx, rval);
289 return JS_TRUE;
292 static JSBool
293 window_open(JSContext *ctx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
295 struct view_state *vs = JS_GetPrivate(ctx, obj);
296 struct document_view *doc_view = vs->doc_view;
297 struct session *ses = doc_view->session;
298 unsigned char *target = "";
299 unsigned char *url;
300 struct uri *uri;
301 static time_t ratelimit_start;
302 static int ratelimit_count;
304 if (get_opt_bool("ecmascript.block_window_opening")) {
305 #ifdef CONFIG_LEDS
306 set_led_value(ses->status.popup_led, 'P');
307 #endif
308 return JS_TRUE;
311 if (argc < 1) return JS_TRUE;
313 url = jsval_to_string(ctx, &argv[0]);
314 if (argc > 1) {
315 JSString *url_string = JS_ValueToString(ctx, argv[0]);
316 JSString *target_string = JS_ValueToString(ctx, argv[1]);
317 int i;
318 #define NUMBER_OF_URLS_TO_REMEMBER 8
319 static struct {
320 JSString *url;
321 JSString *frame;
322 } strings[NUMBER_OF_URLS_TO_REMEMBER];
323 static int indeks;
325 target = jsval_to_string(ctx, &argv[1]);
326 for (i = 0; i < NUMBER_OF_URLS_TO_REMEMBER; i++) {
327 if (!(strings[i].url && strings[i].frame))
328 continue;
329 if (!JS_CompareStrings(url_string, strings[i].url)
330 && !JS_CompareStrings(target_string, strings[i].frame))
331 return JS_TRUE;
333 strings[indeks].url = JS_InternString(ctx, url);
334 strings[indeks].frame = JS_InternString(ctx, target);
335 indeks++;
336 if (indeks >= NUMBER_OF_URLS_TO_REMEMBER) indeks = 0;
337 #undef NUMBER_OF_URLS_TO_REMEMBER
340 /* Ratelimit window opening. Recursive window.open() is very nice.
341 * We permit at most 20 tabs in 2 seconds. The ratelimiter is very
342 * rough but shall suffice against the usual cases. */
343 if (!ratelimit_start || time(NULL) - ratelimit_start > 2) {
344 ratelimit_start = time(NULL);
345 ratelimit_count = 0;
346 } else {
347 ratelimit_count++;
348 if (ratelimit_count > 20)
349 return JS_TRUE;
352 /* TODO: Support for window naming and perhaps some window features? */
354 url = join_urls(doc_view->document->uri,
355 trim_chars(url, ' ', 0));
356 if (!url) return JS_TRUE;
357 uri = get_uri(url, 0);
358 mem_free(url);
359 if (!uri) return JS_TRUE;
362 if (*target && strcasecmp(target, "_blank")) {
363 struct delayed_open *deo = mem_calloc(1, sizeof(*deo));
365 if (deo) {
366 deo->ses = ses;
367 deo->uri = get_uri_reference(uri);
368 deo->target = stracpy(target);
369 register_bottom_half(delayed_goto_uri_frame, deo);
370 boolean_to_jsval(ctx, rval, 1);
371 goto end;
375 if (!get_cmd_opt_bool("no-connect")
376 && !get_cmd_opt_bool("no-home")
377 && !get_cmd_opt_bool("anonymous")
378 && can_open_in_new(ses->tab->term)) {
379 open_uri_in_new_window(ses, uri, NULL, ENV_ANY,
380 CACHE_MODE_NORMAL, TASK_NONE);
381 boolean_to_jsval(ctx, rval, 1);
382 } else {
383 /* When opening a new tab, we might get rerendered, losing our
384 * context and triggerring a disaster, so postpone that. */
385 struct delayed_open *deo = mem_calloc(1, sizeof(*deo));
387 if (deo) {
388 deo->ses = ses;
389 deo->uri = get_uri_reference(uri);
390 register_bottom_half(delayed_open, deo);
391 boolean_to_jsval(ctx, rval, 1);
392 } else {
393 undef_to_jsval(ctx, rval);
397 end:
398 done_uri(uri);
400 return JS_TRUE;