1 /* ECMAScript browser scripting module */
11 #include "config/home.h"
12 #include "ecmascript/spidermonkey-shared.h"
13 #include "intl/charsets.h"
14 #include "main/module.h"
15 #include "osdep/osdep.h"
16 #include "scripting/scripting.h"
17 #include "scripting/smjs/core.h"
18 #include "scripting/smjs/elinks_object.h"
19 #include "scripting/smjs/global_object.h"
20 #include "scripting/smjs/smjs.h"
21 #include "util/file.h"
22 #include "util/string.h"
25 #define SMJS_HOOKS_FILENAME "hooks.js"
28 JSObject
*smjs_elinks_object
;
29 struct session
*smjs_ses
;
33 alert_smjs_error(unsigned char *msg
)
35 report_scripting_error(&smjs_scripting_module
,
40 error_reporter(JSContext
*ctx
, const char *message
, JSErrorReport
*report
)
42 unsigned char *strict
, *exception
, *warning
, *error
;
45 if (!init_string(&msg
)) goto reported
;
47 strict
= JSREPORT_IS_STRICT(report
->flags
) ? " strict" : "";
48 exception
= JSREPORT_IS_EXCEPTION(report
->flags
) ? " exception" : "";
49 warning
= JSREPORT_IS_WARNING(report
->flags
) ? " warning" : "";
50 error
= !report
->flags
? " error" : "";
52 add_format_to_string(&msg
, "A client script raised the following%s%s%s%s",
53 strict
, exception
, warning
, error
);
55 add_to_string(&msg
, ":\n\n");
56 add_to_string(&msg
, message
);
58 if (report
->linebuf
&& report
->tokenptr
) {
59 int pos
= report
->tokenptr
- report
->linebuf
;
61 add_format_to_string(&msg
, "\n\n%s\n.%*s^%*s.",
64 strlen(report
->linebuf
) - pos
- 1, " ");
67 alert_smjs_error(msg
.source
);
71 JS_ClearPendingException(ctx
);
75 smjs_do_file(unsigned char *path
)
81 if (!init_string(&script
)) return 0;
83 if (!add_file_to_string(&script
, path
)
84 || JS_FALSE
== JS_EvaluateScript(smjs_ctx
,
85 JS_GetGlobalObject(smjs_ctx
),
86 script
.source
, script
.length
, path
, 1, &rval
)) {
87 alert_smjs_error("error loading script file");
97 smjs_do_file_wrapper(JSContext
*ctx
, uintN argc
, jsval
*rval
)
99 jsval
*argv
= JS_ARGV(ctx
, rval
);
100 JSString
*jsstr
= JS_ValueToString(smjs_ctx
, *argv
);
101 unsigned char *path
= JS_EncodeString(smjs_ctx
, jsstr
);
103 if (smjs_do_file(path
))
110 smjs_load_hooks(void)
117 path
= straconcat(elinks_home
, SMJS_HOOKS_FILENAME
,
118 (unsigned char *) NULL
);
120 path
= stracpy(CONFDIR STRING_DIR_SEP SMJS_HOOKS_FILENAME
);
123 if (file_exists(path
))
129 init_smjs(struct module
*module
)
131 if (!spidermonkey_runtime_addref()) return;
133 smjs_ctx
= JS_NewContext(spidermonkey_runtime
, 8192);
135 spidermonkey_runtime_release();
139 JS_SetOptions(smjs_ctx
, JSOPTION_VAROBJFIX
| JSOPTION_JIT
| JSOPTION_METHODJIT
);
140 JS_SetVersion(smjs_ctx
, JSVERSION_LATEST
);
142 JS_SetErrorReporter(smjs_ctx
, error_reporter
);
144 smjs_init_global_object();
146 smjs_init_elinks_object();
148 JS_DefineFunction(smjs_ctx
, smjs_global_object
, "do_file",
149 &smjs_do_file_wrapper
, 1, 0);
155 cleanup_smjs(struct module
*module
)
157 if (!smjs_ctx
) return;
159 /* JS_DestroyContext also collects garbage in the JSRuntime.
160 * Because the JSObjects created in smjs_ctx have not been
161 * made visible to any other JSContext, and the garbage
162 * collector of SpiderMonkey is precise, SpiderMonkey
163 * finalizes all of those objects, so cache_entry_finalize
164 * gets called and resets each cache_entry.jsobject = NULL.
165 * If the garbage collector were conservative, ELinks would
166 * have to call smjs_detach_cache_entry_object on each cache
167 * entry before it releases the runtime here. */
168 JS_DestroyContext(smjs_ctx
);
169 spidermonkey_runtime_release();
172 /** Convert a UTF-8 string to a JSString.
175 * Allocate the string in this JSContext.
177 * The input string that should be converted.
179 * Length of @a str in bytes, or -1 if it is null-terminated.
181 * @return the new string. On error, report the error to SpiderMonkey
182 * and return NULL. */
184 utf8_to_jsstring(JSContext
*ctx
, const unsigned char *str
, int length
)
187 const unsigned char *in_end
;
194 in_bytes
= strlen(str
);
198 /* Each byte of input can become at most one UTF-16 unit.
199 * Check whether the multiplication could overflow. */
200 assert(!needs_utf16_surrogates(UCS_REPLACEMENT_CHARACTER
));
201 if (in_bytes
> ((size_t) -1) / sizeof(jschar
)) {
202 #ifdef HAVE_JS_REPORTALLOCATIONOVERFLOW
203 JS_ReportAllocationOverflow(ctx
);
205 JS_ReportOutOfMemory(ctx
);
209 utf16_alloc
= in_bytes
;
210 /* Use malloc because SpiderMonkey will handle the memory after
211 * this routine finishes. */
212 utf16
= malloc(utf16_alloc
* sizeof(jschar
));
214 JS_ReportOutOfMemory(ctx
);
218 in_end
= str
+ in_bytes
;
222 unicode_val_T unicode
;
224 unicode
= utf8_to_unicode((unsigned char **) &str
, in_end
);
225 if (unicode
== UCS_NO_CHAR
)
228 if (needs_utf16_surrogates(unicode
)) {
229 assert(utf16_alloc
- utf16_used
>= 2);
230 if_assert_failed
{ free(utf16
); return NULL
; }
231 utf16
[utf16_used
++] = get_utf16_high_surrogate(unicode
);
232 utf16
[utf16_used
++] = get_utf16_low_surrogate(unicode
);
234 assert(utf16_alloc
- utf16_used
>= 1);
235 if_assert_failed
{ free(utf16
); return NULL
; }
236 utf16
[utf16_used
++] = unicode
;
240 jsstr
= JS_NewUCString(ctx
, utf16
, utf16_used
);
241 /* Do not free if JS_NewUCString was successful because it takes over
242 * handling of the memory. */
243 if (jsstr
== NULL
) free(utf16
);
248 /** Convert a jschar array to UTF-8 and append it to struct string.
249 * Replace misused surrogate codepoints with UCS_REPLACEMENT_CHARACTER.
251 * @param[in,out] utf8
252 * The function appends characters to this UTF-8 string.
255 * Pointer to the first element in an array of jschars.
258 * Number of jschars in the @a utf16 array.
260 * @return @a utf8 if successful, or NULL if not. */
261 static struct string
*
262 add_jschars_to_utf8_string(struct string
*utf8
,
263 const jschar
*utf16
, size_t len
)
267 for (pos
= 0; pos
< len
; ) {
268 unicode_val_T unicode
= utf16
[pos
++];
270 if (is_utf16_surrogate(unicode
)) {
271 if (is_utf16_high_surrogate(unicode
)
273 && is_utf16_low_surrogate(utf16
[pos
])) {
274 unicode
= join_utf16_surrogates(unicode
,
277 unicode
= UCS_REPLACEMENT_CHARACTER
;
282 if (!add_char_to_string(utf8
, '\0'))
285 if (!add_to_string(utf8
, encode_utf8(unicode
)))
293 /** Convert a JSString to a UTF-8 string.
296 * For reporting errors.
298 * The input string that should be converted. Must not be NULL.
300 * Optional. The number of bytes in the returned string,
301 * not counting the terminating null.
303 * @return the new string, which the caller must eventually free
304 * with mem_free(). On error, report the error to SpiderMonkey
305 * and return NULL; *@a length is then undefined. */
307 jsstring_to_utf8(JSContext
*ctx
, JSString
*jsstr
, int *length
)
313 utf16_len
= JS_GetStringLength(jsstr
);
314 utf16
= JS_GetStringCharsZ(ctx
, jsstr
); /* stays owned by jsstr */
316 /* JS_GetStringChars doesn't have a JSContext *
317 * parameter so it can't report the error
318 * (and can't collect garbage either). */
319 JS_ReportOutOfMemory(ctx
);
323 if (!init_string(&utf8
)) {
324 JS_ReportOutOfMemory(ctx
);
328 if (!add_jschars_to_utf8_string(&utf8
, utf16
, utf16_len
)) {
330 JS_ReportOutOfMemory(ctx
);
335 *length
= utf8
.length
;