/* The SpiderMonkey ECMAScript backend. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "elinks.h" #include "ecmascript/spidermonkey/util.h" #include #include "bfu/dialog.h" #include "cache/cache.h" #include "config/options.h" #include "cookies/cookies.h" #include "dialogs/menu.h" #include "dialogs/status.h" #include "document/html/frames.h" #include "document/document.h" #include "document/forms.h" #include "document/view.h" #include "ecmascript/ecmascript.h" #include "ecmascript/spidermonkey.h" #include "ecmascript/spidermonkey/console.h" #include "ecmascript/spidermonkey/document.h" #include "ecmascript/spidermonkey/form.h" #include "ecmascript/spidermonkey/heartbeat.h" #include "ecmascript/spidermonkey/location.h" #include "ecmascript/spidermonkey/localstorage.h" #include "ecmascript/spidermonkey/navigator.h" #include "ecmascript/spidermonkey/screen.h" #include "ecmascript/spidermonkey/unibar.h" #include "ecmascript/spidermonkey/window.h" #include "intl/gettext/libintl.h" #include "main/select.h" #include "osdep/newwin.h" #include "osdep/sysname.h" #include "protocol/http/http.h" #include "protocol/uri.h" #include "session/history.h" #include "session/location.h" #include "session/session.h" #include "session/task.h" #include "terminal/tab.h" #include "terminal/terminal.h" #include "util/conv.h" #include "util/string.h" #include "viewer/text/draw.h" #include "viewer/text/form.h" #include "viewer/text/link.h" #include "viewer/text/view.h" #include "viewer/text/vs.h" #include /*** Global methods */ /* TODO? Are there any which need to be implemented? */ static int js_module_init_ok; bool PrintError(JSContext* cx, FILE* file, JS::ConstUTF8CharsZ toStringResult, JSErrorReport* report, bool reportWarnings) { MOZ_ASSERT(report); /* Conditionally ignore reported warnings. */ if (JSREPORT_IS_WARNING(report->flags) && !reportWarnings) return false; char* prefix = nullptr; if (report->filename) prefix = JS_smprintf("%s:", report->filename); if (report->lineno) { char* tmp = prefix; prefix = JS_smprintf("%s%u:%u ", tmp ? tmp : "", report->lineno, report->column); JS_free(cx, tmp); } if (JSREPORT_IS_WARNING(report->flags)) { char* tmp = prefix; prefix = JS_smprintf("%s%swarning: ", tmp ? tmp : "", JSREPORT_IS_STRICT(report->flags) ? "strict " : ""); JS_free(cx, tmp); } const char* message = toStringResult ? toStringResult.c_str() : report->message().c_str(); /* embedded newlines -- argh! */ const char* ctmp; while ((ctmp = strchr(message, '\n')) != 0) { ctmp++; if (prefix) fputs(prefix, file); fwrite(message, 1, ctmp - message, file); message = ctmp; } /* If there were no filename or lineno, the prefix might be empty */ if (prefix) fputs(prefix, file); fputs(message, file); if (const char16_t* linebuf = report->linebuf()) { size_t n = report->linebufLength(); fputs(":\n", file); if (prefix) fputs(prefix, file); for (size_t i = 0; i < n; i++) fputc(static_cast(linebuf[i]), file); // linebuf usually ends with a newline. If not, add one here. if (n == 0 || linebuf[n-1] != '\n') fputc('\n', file); if (prefix) fputs(prefix, file); n = report->tokenOffset(); for (size_t i = 0, j = 0; i < n; i++) { if (linebuf[i] == '\t') { for (size_t k = (j + 8) & ~7; j < k; j++) fputc('.', file); continue; } fputc('.', file); j++; } fputc('^', file); } fputc('\n', file); fflush(file); JS_free(cx, prefix); return true; } static void error_reporter(JSContext *ctx, JSErrorReport *report) { JSCompartment *comp = js::GetContextCompartment(ctx); if (!comp) { return; } struct ecmascript_interpreter *interpreter = JS_GetCompartmentPrivate(comp); struct session *ses = interpreter->vs->doc_view->session; struct terminal *term; char *strict, *exception, *warning, *error; struct string msg; char str_lineno[256]=""; char *prefix = nullptr; assert(interpreter && interpreter->vs && interpreter->vs->doc_view && ses && ses->tab); if_assert_failed goto reported; term = ses->tab->term; #ifdef CONFIG_LEDS set_led_value(ses->status.ecmascript_led, 'J'); #endif if (!get_opt_bool("ecmascript.error_reporting", ses) || !init_string(&msg)) goto reported; strict = JSREPORT_IS_STRICT(report->flags) ? " strict" : ""; exception = JSREPORT_IS_EXCEPTION(report->flags) ? " exception" : ""; warning = JSREPORT_IS_WARNING(report->flags) ? " warning" : ""; error = !report->flags ? " error" : ""; add_format_to_string(&msg, _("A script embedded in the current " "document raised the following%s%s%s%s", term), strict, exception, warning, error); /* Report message and line number of SpiderMonkey error */ /* Sometimes the line number is zero */ add_to_string(&msg, "\n\n"); add_to_string(&msg, report->message().c_str()); sprintf(str_lineno,"\n at line: %d",report->lineno); add_to_string(&msg, str_lineno); info_box(term, MSGBOX_FREE_TEXT, N_("JavaScript Error"), ALIGN_CENTER, msg.source); reported: /* Im clu'les. --pasky */ JS_ClearPendingException(ctx); } static void spidermonkey_init(struct module *xxx) { js_module_init_ok = spidermonkey_runtime_addref(); } static void spidermonkey_done(struct module *xxx) { if (js_module_init_ok) spidermonkey_runtime_release(); } void * spidermonkey_get_interpreter(struct ecmascript_interpreter *interpreter) { JSContext *ctx; JSObject *console_obj, *document_obj, /* *forms_obj,*/ *history_obj, *location_obj, *statusbar_obj, *menubar_obj, *navigator_obj, *localstorage_obj, *screen_obj; static int initialized = 0; assert(interpreter); if (!js_module_init_ok) return NULL; ctx = main_ctx; if (!ctx) { return nullptr; } interpreter->backend_data = ctx; interpreter->ar = new JSAutoRequest(ctx); // JSAutoRequest ar(ctx); // JS_SetContextPrivate(ctx, interpreter); // JS_SetOptions(main_ctx, JSOPTION_VAROBJFIX | JS_METHODJIT); /* This is obsolete since mozjs52 */ //JS::SetWarningReporter(ctx, error_reporter); JS_AddInterruptCallback(ctx, heartbeat_callback); JS::CompartmentOptions options; JS::RootedObject window_obj(ctx, JS_NewGlobalObject(ctx, &window_class, NULL, JS::FireOnNewGlobalHook, options)); if (window_obj) { interpreter->ac = window_obj; interpreter->ac2 = new JSAutoCompartment(ctx, window_obj); } else { goto release_and_fail; } if (!JS_InitStandardClasses(ctx, window_obj)) { goto release_and_fail; } if (!JS_DefineProperties(ctx, window_obj, window_props)) { goto release_and_fail; } if (!spidermonkey_DefineFunctions(ctx, window_obj, window_funcs)) { goto release_and_fail; } //JS_SetPrivate(window_obj, interpreter); /* to @window_class */ document_obj = spidermonkey_InitClass(ctx, window_obj, NULL, &document_class, NULL, 0, document_props, document_funcs, NULL, NULL); if (!document_obj) { goto release_and_fail; } interpreter->document_obj = document_obj; /* forms_obj = spidermonkey_InitClass(ctx, document_obj, NULL, &forms_class, NULL, 0, forms_props, forms_funcs, NULL, NULL); if (!forms_obj) { goto release_and_fail; } */ history_obj = spidermonkey_InitClass(ctx, window_obj, NULL, &history_class, NULL, 0, (JSPropertySpec *) NULL, history_funcs, NULL, NULL); if (!history_obj) { goto release_and_fail; } location_obj = spidermonkey_InitClass(ctx, window_obj, NULL, &location_class, NULL, 0, location_props, location_funcs, NULL, NULL); if (!location_obj) { goto release_and_fail; } screen_obj = spidermonkey_InitClass(ctx, window_obj, NULL, &screen_class, NULL, 0, screen_props, NULL, NULL, NULL); if (!screen_obj) { goto release_and_fail; } menubar_obj = JS_InitClass(ctx, window_obj, nullptr, &menubar_class, NULL, 0, unibar_props, NULL, NULL, NULL); if (!menubar_obj) { goto release_and_fail; } JS_SetPrivate(menubar_obj, "t"); /* to @menubar_class */ statusbar_obj = JS_InitClass(ctx, window_obj, nullptr, &statusbar_class, NULL, 0, unibar_props, NULL, NULL, NULL); if (!statusbar_obj) { goto release_and_fail; } JS_SetPrivate(statusbar_obj, "s"); /* to @statusbar_class */ navigator_obj = JS_InitClass(ctx, window_obj, nullptr, &navigator_class, NULL, 0, navigator_props, NULL, NULL, NULL); if (!navigator_obj) { goto release_and_fail; } console_obj = spidermonkey_InitClass(ctx, window_obj, NULL, &console_class, NULL, 0, console_props, console_funcs, NULL, NULL); if (!console_obj) { goto release_and_fail; } localstorage_obj = spidermonkey_InitClass(ctx, window_obj, NULL, &localstorage_class, NULL, 0, localstorage_props, localstorage_funcs, NULL, NULL); if (!localstorage_obj) { goto release_and_fail; } JS_SetCompartmentPrivate(js::GetContextCompartment(ctx), interpreter); return ctx; release_and_fail: spidermonkey_put_interpreter(interpreter); return NULL; } void spidermonkey_put_interpreter(struct ecmascript_interpreter *interpreter) { JSContext *ctx; assert(interpreter); if (!js_module_init_ok) return; ctx = interpreter->backend_data; if (interpreter->ac2) { delete (JSAutoCompartment *)interpreter->ac2; } if (interpreter->ar) { delete (JSAutoRequest *)interpreter->ar; } // JS_DestroyContext(ctx); interpreter->backend_data = NULL; interpreter->ac = nullptr; interpreter->ac2 = nullptr; interpreter->ar = nullptr; } void spidermonkey_check_for_exception(JSContext *ctx) { if (JS_IsExceptionPending(ctx)) { JS::RootedValue exception(ctx); if(JS_GetPendingException(ctx,&exception) && exception.isObject()) { JS::AutoSaveExceptionState savedExc(ctx); JS::Rooted exceptionObject(ctx, &exception.toObject()); JSErrorReport *report = JS_ErrorFromException(ctx,exceptionObject); if(report) { if (report->lineno>0) { /* Somehow the reporter alway reports first error * Undefined and with line 0. Let's filter this. */ /* Optional printing javascript error to file */ //FILE *f = fopen("js.err","a"); //PrintError(ctx, f, report->message(), report, true); /* Send the error to the tui */ error_reporter(ctx, report); //DBG("file: %s",report->filename); //DBG("file: %s",report->message()); //DBG("file: %d",(int) report->lineno); } } //JS_ClearPendingException(ctx); } /* This absorbs all following exceptions * probably not the 100% correct solution * to the javascript error handling but * at least there isn't too much click-bait * on each site with javascript enabled */ JS_ClearPendingException(ctx); } } void free_document(void *doc) { if (!doc) { return; } xmlpp::Document *docu = doc; delete docu; } static void delayed_reload(void *data) { struct delayed_rel *rel = data; assert(rel); doc_rerender_after_document_update(rel->ses); free_document(rel->doc); mem_free(rel); } static void check_for_rerender(struct ecmascript_interpreter *interpreter) { if (interpreter->changed) { struct document_view *doc_view = interpreter->vs->doc_view; struct document *document = doc_view->document; struct session *ses = doc_view->session; struct cache_entry *cached = document->cached; struct fragment *f = get_cache_fragment(cached); if (document->dom && f && f->length) { xmlpp::Document *docu = (xmlpp::Document *)document->dom; std::string doc1_string = docu->write_to_string_formatted(); //delete docu; document->dom = NULL; size_t fd_len=f->length; delete_entry_content(cached); /* This is very ugly, indeed. And Yes fd_len isn't * logically correct. But using nu_len will cause * the document to render improperly. * TBD: somehow better rerender the document * now it's places on the session level in doc_loading_callback */ int ret = add_fragment(cached, 0, doc1_string.c_str(), doc1_string.size()); normalize_cache_entry(cached, doc1_string.size()); document->ecmascript_counter++; interpreter->changed = false; struct delayed_rel *rel = mem_calloc(1, sizeof(*rel)); if (rel) { rel->ses = ses; rel->doc = docu; register_bottom_half(delayed_reload, rel); } } } } void spidermonkey_eval(struct ecmascript_interpreter *interpreter, struct string *code, struct string *ret) { JSContext *ctx; JS::Value rval; assert(interpreter); if (!js_module_init_ok) { return; } ctx = interpreter->backend_data; JS_BeginRequest(ctx); JSCompartment *comp = JS_EnterCompartment(ctx, interpreter->ac); interpreter->heartbeat = add_heartbeat(interpreter); interpreter->ret = ret; JS::RootedObject cg(ctx, JS::CurrentGlobalOrNull(ctx)); JS::RootedValue r_val(ctx, rval); JS::CompileOptions options(ctx); JS::Evaluate(ctx, options, code->source, code->length, &r_val); spidermonkey_check_for_exception(ctx); done_heartbeat(interpreter->heartbeat); JS_LeaveCompartment(ctx, comp); JS_EndRequest(ctx); check_for_rerender(interpreter); } void spidermonkey_call_function(struct ecmascript_interpreter *interpreter, JS::HandleValue fun, struct string *ret) { JSContext *ctx; JS::Value rval; assert(interpreter); if (!js_module_init_ok) { return; } ctx = interpreter->backend_data; JS_BeginRequest(ctx); JSCompartment *comp = JS_EnterCompartment(ctx, interpreter->ac); interpreter->heartbeat = add_heartbeat(interpreter); interpreter->ret = ret; JS::RootedValue r_val(ctx, rval); JS::RootedObject cg(ctx, JS::CurrentGlobalOrNull(ctx)); JS_CallFunctionValue(ctx, cg, fun, JS::HandleValueArray::empty(), &r_val); done_heartbeat(interpreter->heartbeat); JS_LeaveCompartment(ctx, comp); JS_EndRequest(ctx); check_for_rerender(interpreter); } char * spidermonkey_eval_stringback(struct ecmascript_interpreter *interpreter, struct string *code) { bool ret; JSContext *ctx; JS::Value rval; char *result = NULL; assert(interpreter); if (!js_module_init_ok) return NULL; ctx = interpreter->backend_data; interpreter->ret = NULL; interpreter->heartbeat = add_heartbeat(interpreter); JS_BeginRequest(ctx); JSCompartment *comp = JS_EnterCompartment(ctx, interpreter->ac); JS::RootedObject cg(ctx, JS::CurrentGlobalOrNull(ctx)); JS::RootedValue r_rval(ctx, rval); JS::CompileOptions options(ctx); // options.setIntroductionType("js shell load") // .setUTF8(true) // .setCompileAndGo(true) // .setNoScriptRval(true); ret = JS::Evaluate(ctx, options, code->source, code->length, &r_rval); done_heartbeat(interpreter->heartbeat); if (ret == false) { result = NULL; } else if (r_rval.isNullOrUndefined()) { /* Undefined value. */ result = NULL; } else { result = stracpy(JS_EncodeString(ctx, r_rval.toString())); } JS_LeaveCompartment(ctx, comp); JS_EndRequest(ctx); check_for_rerender(interpreter); return result; } int spidermonkey_eval_boolback(struct ecmascript_interpreter *interpreter, struct string *code) { JSContext *ctx; JS::Value rval; int ret; int result = 0; assert(interpreter); if (!js_module_init_ok) return 0; ctx = interpreter->backend_data; interpreter->ret = NULL; JSCompartment *comp = JS_EnterCompartment(ctx, interpreter->ac); JS_BeginRequest(ctx); JS::RootedFunction fun(ctx); JS::CompileOptions options(ctx); JS::AutoObjectVector ag(ctx); if (!JS::CompileFunction(ctx, ag, options, "aaa", 0, nullptr, code->source, code->length, &fun)) { return -1; }; interpreter->heartbeat = add_heartbeat(interpreter); JS::RootedValue r_val(ctx, rval); JS::RootedObject cg(ctx, JS::CurrentGlobalOrNull(ctx)); ret = JS_CallFunction(ctx, cg, fun, JS::HandleValueArray::empty(), &r_val); done_heartbeat(interpreter->heartbeat); if (ret == 2) { /* onClick="history.back()" */ result = 0; } else if (ret == false) { result = -1; } else if (r_val.isUndefined()) { /* Undefined value. */ result = -1; } else { result = r_val.toBoolean(); } JS_LeaveCompartment(ctx, comp); JS_EndRequest(ctx); check_for_rerender(interpreter); return result; } struct module spidermonkey_module = struct_module( /* name: */ N_("SpiderMonkey"), /* options: */ NULL, /* events: */ NULL, /* submodules: */ NULL, /* data: */ NULL, /* init: */ spidermonkey_init, /* done: */ spidermonkey_done );