1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-06-14 23:32:27 +00:00

[click] Added eventListener. It works for these two test cases.

This commit is contained in:
Witold Filipczyk 2022-11-07 20:59:19 +01:00
parent 3330427738
commit d7f4f94a62
15 changed files with 383 additions and 13 deletions

View File

@ -82,6 +82,19 @@
static INIT_LIST_OF(struct document, format_cache);
const char *script_event_hook_name[] = {
"click",
"dblclick",
"mouseover",
"hover",
"focus",
"mouseout",
"blur",
"keydown",
"keyup",
NULL
};
#ifdef HAVE_INET_NTOP
/* DNS callback. */
static void

View File

@ -90,6 +90,9 @@ enum script_event_hook_type {
SEVHOOK_ONKEYUP
};
/* keep in sync with above */
extern const char *script_event_hook_name[];
struct script_event_hook {
LIST_HEAD(struct script_event_hook);
@ -102,6 +105,9 @@ struct link {
enum link_type type;
#ifdef CONFIG_ECMASCRIPT
int element_offset;
#endif
char *where;
char *target;
char *where_img;
@ -214,6 +220,7 @@ struct document {
LIST_OF(struct ecmascript_timeout) timeouts;
int ecmascript_counter;
void *dom;
void *element_map;
char *text;
void *forms_nodeset;
#endif

View File

@ -76,6 +76,8 @@ struct text_attrib {
char *onblur;
char *onkeydown;
char *onkeyup;
char *top_name;
};
/* This enum is pretty ugly, yes ;). */

View File

@ -141,13 +141,14 @@ init_form_control(enum form_type type, char *attr,
void
html_button(struct html_context *html_context, char *a,
char *xxx3, char *xxx4, char **xxx5)
char *html, char *xxx4, char **xxx5)
{
char *al;
struct el_form_control *fc;
enum form_type type = FC_SUBMIT;
int cp = html_context->doc_cp;
elformat.top_name = html_top->name;
html_focusable(html_context, a);
al = get_attr_val(a, "type", cp);
@ -192,9 +193,11 @@ html_input_format(struct html_context *html_context, char *a,
struct el_form_control *fc)
{
put_chrs(html_context, " ", 1);
char *top_name = html_top->name;
html_stack_dup(html_context, ELEMENT_KILLABLE);
html_focusable(html_context, a);
elformat.form = fc;
elformat.top_name = top_name;
mem_free_if(elformat.title);
elformat.title = get_attr_val(a, "title", html_context->doc_cp);
switch (fc->type) {
@ -354,6 +357,7 @@ do_html_select(char *attr, char *html,
int i, max_width;
int closing_tag;
elformat.top_name = html_top->name;
html_focusable(html_context, attr);
init_menu(&lnk_menu);
@ -529,6 +533,7 @@ do_html_select_multiple(struct html_context *html_context, char *a,
char *al = get_attr_val(a, "name", html_context->doc_cp);
if (!al) return;
elformat.top_name = html_top->name;
html_focusable(html_context, a);
html_top->type = ELEMENT_DONT_KILL;
mem_free_set(&elformat.select, al);
@ -636,6 +641,7 @@ html_textarea(struct html_context *html_context, char *attr,
int cols, rows;
int i;
elformat.top_name = html_top->name;
html_focusable(html_context, attr);
while (html < eof && (*html == '\n' || *html == '\r')) html++;
p = html;

View File

@ -1047,6 +1047,7 @@ html_frame(struct html_context *html_context, char *a,
if (!name) return;
if (!html_context->options->frames || !html_top->frameset) {
elformat.top_name = html_top->name;
html_focusable(html_context, a);
put_link_line("Frame: ", name, url, "", html_context);

View File

@ -94,6 +94,7 @@ html_a(struct html_context *html_context, char *a,
mem_free_set(&elformat.title,
get_attr_val(a, "title", html_context->doc_cp));
elformat.top_name = html_top->name;
html_focusable(html_context, a);
} else {
@ -205,6 +206,7 @@ put_image_label(char *a, char *label,
/* This is not 100% appropriate for <img>, but well, accepting
* accesskey and tabindex near <img> is just our little
* extension to the standard. After all, it makes sense. */
elformat.top_name = html_top->name;
html_focusable(html_context, a);
saved_foreground = elformat.style.color.foreground;
@ -381,7 +383,7 @@ html_source(struct html_context *html_context, char *a,
mem_free_set(&title, get_image_filename_from_src(options->image_link.filename_maxlen, src));
}
}
elformat.top_name = html_top->name;
html_focusable(html_context, a);
if (title && *title) {
@ -433,7 +435,7 @@ html_applet(struct html_context *html_context, char *a,
if (!code) return;
alt = get_attr_val(a, "alt", html_context->doc_cp);
elformat.top_name = html_top->name;
html_focusable(html_context, a);
if (alt && *alt) {
@ -459,6 +461,7 @@ html_audio(struct html_context *html_context, char *a,
url = get_url_val(a, "src", html_context->doc_cp);
if (!url) return;
elformat.top_name = html_top->name;
html_focusable(html_context, a);
put_link_line("Audio: ", basename(url), url,
@ -489,6 +492,7 @@ html_iframe_do(char *a, char *object_src,
return;
}
elformat.top_name = html_top->name;
html_focusable(html_context, a);
if (html_context->options->iframes) {
@ -574,6 +578,7 @@ html_object(struct html_context *html_context, char *a,
name = get_attr_val(a, "standby", html_context->doc_cp);
elformat.top_name = html_top->name;
html_focusable(html_context, a);
if (name && *name) {
@ -638,6 +643,7 @@ html_video(struct html_context *html_context, char *a,
url = get_url_val(a, "src", html_context->doc_cp);
if (!url) return;
elformat.top_name = html_top->name;
html_focusable(html_context, a);
put_link_line("Video: ", basename(url), url,
@ -944,6 +950,7 @@ html_link(struct html_context *html_context, char *a,
if (!name) goto free_and_return;
if (!init_string(&text)) goto free_and_return;
elformat.top_name = html_top->name;
html_focusable(html_context, a);
if (link.title) {

View File

@ -1551,6 +1551,10 @@ new_link(struct html_context *html_context, const char *name, int namelen)
? elformat.style.color.foreground
: elformat.color.clink;
#ifdef CONFIG_ECMASCRIPT
link->element_offset = elformat.top_name ? elformat.top_name - document->text : 0;
#endif
init_link_event_hooks(html_context, link);
document->links_sorted = 0;

View File

@ -293,8 +293,8 @@ render_encoded_document(struct cache_entry *cached, struct document *document)
&& (!c_strlcasecmp("text/gemini", 11, cached->content_type, -1)))
render_gemini_document(cached, document, &buffer);
else
#ifdef CONFIG_XML
if (false) render_xhtml_document(cached, document, &buffer);
#if defined(CONFIG_XML) && defined(CONFIG_ECMASCRIPT)
if (true) render_xhtml_document(cached, document, NULL);
else
#endif
render_html_document(cached, document, &buffer);

View File

@ -38,6 +38,8 @@
#include "util/string.h"
#include <libxml++/libxml++.h>
#include <map>
#if 0
@ -267,6 +269,65 @@ dump_dom_structure(struct source_renderer *renderer, void *nod, int depth)
}
#endif
static void
dump_element(std::map<int, xmlpp::Element *> *mapa, struct string *buf, xmlpp::Element *element)
{
add_char_to_string(buf, '<');
(*mapa)[buf->length] = element;
add_to_string(buf, element->get_name().c_str());
auto attrs = element->get_attributes();
auto it = attrs.begin();
auto end = attrs.end();
for (;it != end; ++it) {
add_char_to_string(buf, ' ');
add_to_string(buf, (*it)->get_name().c_str());
add_char_to_string(buf, '=');
add_char_to_string(buf, '"');
add_to_string(buf, (*it)->get_value().c_str());
add_char_to_string(buf, '"');
}
add_char_to_string(buf, '>');
}
static void
walk_tree(std::map<int, xmlpp::Element *> *mapa, struct string *buf, void *nod, bool start)
{
xmlpp::Node *node = static_cast<xmlpp::Node *>(nod);
if (!start) {
const auto textNode = dynamic_cast<const xmlpp::ContentNode*>(node);
if (textNode) {
add_bytes_to_string(buf, textNode->get_content().c_str(), textNode->get_content().length());
} else {
auto element = dynamic_cast<xmlpp::Element*>(node);
if (element) {
dump_element(mapa, buf, element);
}
}
}
auto childs = node->get_children();
auto it = childs.begin();
auto end = childs.end();
for (; it != end; ++it) {
walk_tree(mapa, buf, *it, false);
}
if (!start) {
const auto element = dynamic_cast<const xmlpp::Element*>(node);
if (element) {
add_to_string(buf, "</");
add_to_string(buf, element->get_name().c_str());
add_char_to_string(buf, '>');
}
}
}
void
render_xhtml_document(struct cache_entry *cached, struct document *document, struct string *buffer)
{
@ -278,7 +339,6 @@ render_xhtml_document(struct cache_entry *cached, struct document *document, str
render_html_document(cached, document, buffer);
return;
}
struct string head;
assert(cached && document);
@ -300,19 +360,29 @@ render_xhtml_document(struct cache_entry *cached, struct document *document, str
}
xmlpp::Document *doc = (xmlpp::Document *)document->dom;
xmlpp::Element* root = (xmlpp::Element *)doc->get_root_node();
if (!buffer) {
xmlpp::ustring text = doc->write_to_string_formatted();
struct string tt;
if (!init_string(&tt)) {
done_string(&head);
return;
}
add_bytes_to_string(&tt, text.c_str(), text.size());
std::map<int, xmlpp::Element *> *mapa = (std::map<int, xmlpp::Element *> *)document->element_map;
if (!mapa) {
mapa = new std::map<int, xmlpp::Element *>;
document->element_map = (void *)mapa;
} else {
mapa->clear();
}
walk_tree(mapa, &tt, root, true);
buffer = &tt;
document->text = tt.source;
}
if (add_to_head) {
mem_free_set(&cached->head, head.source);
}

View File

@ -28,6 +28,7 @@
#include "ecmascript/spidermonkey/attributes.h"
#include "ecmascript/spidermonkey/collection.h"
#include "ecmascript/spidermonkey/element.h"
#include "ecmascript/spidermonkey/heartbeat.h"
#include "ecmascript/spidermonkey/nodelist.h"
#include "ecmascript/spidermonkey/window.h"
#include "intl/libintl.h"
@ -97,6 +98,18 @@ static bool element_set_property_textContent(JSContext *ctx, unsigned int argc,
static bool element_get_property_title(JSContext *ctx, unsigned int argc, JS::Value *vp);
static bool element_set_property_title(JSContext *ctx, unsigned int argc, JS::Value *vp);
struct listener {
LIST_HEAD(struct listener);
char *typ;
JS::RootedValue fun;
};
struct element_private {
LIST_OF(struct listener) listeners;
struct ecmascript_interpreter *interpreter;
JS::RootedObject thisval;
};
static void element_finalize(JS::GCContext *op, JSObject *obj);
JSClassOps element_ops = {
@ -114,7 +127,7 @@ JSClassOps element_ops = {
JSClass element_class = {
"element",
JSCLASS_HAS_RESERVED_SLOTS(1),
JSCLASS_HAS_RESERVED_SLOTS(2),
&element_ops
};
@ -2310,6 +2323,7 @@ element_set_property_title(JSContext *ctx, unsigned int argc, JS::Value *vp)
return true;
}
static bool element_addEventListener(JSContext *ctx, unsigned int argc, JS::Value *rval);
static bool element_appendChild(JSContext *ctx, unsigned int argc, JS::Value *rval);
static bool element_cloneNode(JSContext *ctx, unsigned int argc, JS::Value *rval);
static bool element_closest(JSContext *ctx, unsigned int argc, JS::Value *rval);
@ -2327,9 +2341,11 @@ static bool element_querySelector(JSContext *ctx, unsigned int argc, JS::Value *
static bool element_querySelectorAll(JSContext *ctx, unsigned int argc, JS::Value *rval);
static bool element_remove(JSContext *ctx, unsigned int argc, JS::Value *rval);
static bool element_removeChild(JSContext *ctx, unsigned int argc, JS::Value *rval);
static bool element_removeEventListener(JSContext *ctx, unsigned int argc, JS::Value *rval);
static bool element_setAttribute(JSContext *ctx, unsigned int argc, JS::Value *rval);
const spidermonkeyFunctionSpec element_funcs[] = {
{ "addEventListener", element_addEventListener, 3 },
{ "appendChild", element_appendChild, 1 },
{ "cloneNode", element_cloneNode, 1 },
{ "closest", element_closest, 1 },
@ -2347,6 +2363,7 @@ const spidermonkeyFunctionSpec element_funcs[] = {
{ "querySelectorAll", element_querySelectorAll, 1 },
{ "remove", element_remove, 0 },
{ "removeChild", element_removeChild, 1 },
{ "removeEventListener", element_removeEventListener, 3 },
{ "setAttribute", element_setAttribute, 2 },
{ NULL }
};
@ -2385,6 +2402,137 @@ check_contains(xmlpp::Node *node, xmlpp::Node *searched, bool *result_set, bool
}
}
static bool
element_addEventListener(JSContext *ctx, unsigned int argc, JS::Value *rval)
{
#ifdef ECMASCRIPT_DEBUG
fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
JS::Realm *comp = js::GetContextRealm(ctx);
if (!comp) {
#ifdef ECMASCRIPT_DEBUG
fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif
return false;
}
JS::CallArgs args = CallArgsFromVp(argc, rval);
JS::RootedObject hobj(ctx, &args.thisv().toObject());
struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS::GetRealmPrivate(comp);
if (!JS_InstanceOf(ctx, hobj, &element_class, NULL)) {
#ifdef ECMASCRIPT_DEBUG
fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif
return false;
}
xmlpp::Element *el = JS::GetMaybePtrFromReservedSlot<xmlpp::Element>(hobj, 0);
struct element_private *el_private = JS::GetMaybePtrFromReservedSlot<struct element_private>(hobj, 1);
if (!el || !el_private) {
args.rval().setNull();
return true;
}
if (argc < 2) {
args.rval().setUndefined();
return true;
}
char *method = jsval_to_string(ctx, args[0]);
JS::RootedValue fun(ctx, args[1]);
struct listener *l;
foreach(l, el_private->listeners) {
if (strcmp(l->typ, method)) {
continue;
}
if (l->fun == fun) {
args.rval().setUndefined();
mem_free(method);
return true;
}
}
struct listener *n = (struct listener *)mem_calloc(1, sizeof(*n));
if (n) {
n->typ = method;
n->fun = fun;
add_to_list_end(el_private->listeners, n);
}
args.rval().setUndefined();
return true;
}
static bool
element_removeEventListener(JSContext *ctx, unsigned int argc, JS::Value *rval)
{
#ifdef ECMASCRIPT_DEBUG
fprintf(stderr, "%s:%s\n", __FILE__, __FUNCTION__);
#endif
JS::Realm *comp = js::GetContextRealm(ctx);
if (!comp) {
#ifdef ECMASCRIPT_DEBUG
fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif
return false;
}
JS::CallArgs args = CallArgsFromVp(argc, rval);
JS::RootedObject hobj(ctx, &args.thisv().toObject());
struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS::GetRealmPrivate(comp);
if (!JS_InstanceOf(ctx, hobj, &element_class, NULL)) {
#ifdef ECMASCRIPT_DEBUG
fprintf(stderr, "%s:%s %d\n", __FILE__, __FUNCTION__, __LINE__);
#endif
return false;
}
xmlpp::Element *el = JS::GetMaybePtrFromReservedSlot<xmlpp::Element>(hobj, 0);
struct element_private *el_private = JS::GetMaybePtrFromReservedSlot<struct element_private>(hobj, 1);
if (!el || !el_private) {
args.rval().setNull();
return true;
}
if (argc < 2) {
args.rval().setUndefined();
return true;
}
char *method = jsval_to_string(ctx, args[0]);
if (!method) {
return false;
}
JS::RootedValue fun(ctx, args[1]);
struct listener *l;
foreach(l, el_private->listeners) {
if (strcmp(l->typ, method)) {
continue;
}
if (l->fun == fun) {
del_from_list(l);
mem_free_set(&l->typ, NULL);
mem_free(l);
mem_free(method);
args.rval().setUndefined();
return true;
}
}
mem_free(method);
args.rval().setUndefined();
return true;
}
static bool
element_appendChild(JSContext *ctx, unsigned int argc, JS::Value *rval)
{
@ -3316,14 +3464,34 @@ element_setAttribute(JSContext *ctx, unsigned int argc, JS::Value *rval)
return true;
}
static std::map<void *, struct element_private *> map_privates;
JSObject *
getElement(JSContext *ctx, void *node)
{
auto elem = map_privates.find(node);
struct element_private *el_private = NULL;
if (elem != map_privates.end()) {
el_private = elem->second;
} else {
el_private = (struct element_private *)mem_calloc(1, sizeof(*el_private));
if (!el_private) {
return NULL;
}
init_list(el_private->listeners);
}
JSObject *el = JS_NewObject(ctx, &element_class);
if (!el) {
mem_free(el_private);
return NULL;
}
JS::Realm *comp = js::GetContextRealm(ctx);
struct ecmascript_interpreter *interpreter = (struct ecmascript_interpreter *)JS::GetRealmPrivate(comp);
el_private->interpreter = interpreter;
JS::RootedObject r_el(ctx, el);
@ -3331,6 +3499,39 @@ getElement(JSContext *ctx, void *node)
spidermonkey_DefineFunctions(ctx, el, element_funcs);
JS::SetReservedSlot(el, 0, JS::PrivateValue(node));
JS::SetReservedSlot(el, 1, JS::PrivateValue(el_private));
el_private->thisval = r_el;
map_privates[node] = el_private;
return el;
}
void
check_element_event(void *elem, const char *event_name)
{
auto el = map_privates.find(elem);
if (el == map_privates.end()) {
return;
}
struct element_private *el_private = el->second;
struct ecmascript_interpreter *interpreter = el_private->interpreter;
JSContext *ctx = (JSContext *)interpreter->backend_data;
JS::Realm *comp = JS::EnterRealm(ctx, (JSObject *)interpreter->ac);
JS::RootedValue r_val(ctx);
interpreter->heartbeat = add_heartbeat(interpreter);
struct listener *l;
foreach(l, el_private->listeners) {
if (strcmp(l->typ, event_name)) {
continue;
}
JS_CallFunctionValue(ctx, el_private->thisval, l->fun, JS::HandleValueArray::empty(), &r_val);
}
done_heartbeat(interpreter->heartbeat);
JS::LeaveRealm(ctx, comp);
check_for_rerender(interpreter, event_name);
}

View File

@ -1,4 +1,3 @@
#ifndef EL__ECMASCRIPT_SPIDERMONKEY_ELEMENT_H
#define EL__ECMASCRIPT_SPIDERMONKEY_ELEMENT_H
@ -11,4 +10,6 @@ JSObject *getElement(JSContext *ctx, void *node);
void walk_tree(struct string *buf, void *nod, bool start = true, bool toSortAttrs = false);
void check_element_event(void *elem, const char *event_name);
#endif

View File

@ -112,7 +112,6 @@ struct listener {
JS::RootedValue fun;
};
struct classcomp {
bool operator() (const std::string& lhs, const std::string& rhs) const
{

View File

@ -20,6 +20,12 @@
#include "document/options.h"
#include "document/view.h"
#include "ecmascript/ecmascript.h"
#ifdef CONFIG_ECMASCRIPT_SMJS
#include "ecmascript/spidermonkey/element.h"
#include <libxml++/libxml++.h>
#endif
#include "intl/libintl.h"
#include "main/object.h"
#include "protocol/uri.h"
@ -44,6 +50,7 @@
#include "viewer/text/view.h"
#include "viewer/text/vs.h"
#include <map>
/* Perhaps some of these would be more fun to have in viewer/common/, dunno.
* --pasky */
@ -59,10 +66,21 @@ current_link_evhook(struct document_view *doc_view, enum script_event_hook_type
assert(doc_view && doc_view->vs);
link = get_current_link(doc_view);
if (!link) return -1;
if (!link->event_hooks) return -1;
if (!doc_view->vs->ecmascript) return -1;
std::map<int, xmlpp::Element *> *mapa = (std::map<int, xmlpp::Element *> *)doc_view->document->element_map;
if (mapa) {
auto element = (*mapa).find(link->element_offset);
if (element != (*mapa).end()) {
const char *event_name = script_event_hook_name[(int)type];
check_element_event(element->second, event_name);
}
}
if (!link->event_hooks) return -1;
foreach (evhook, *link->event_hooks) {
char *ret;

View File

@ -0,0 +1,20 @@
<html>
<head>
<script>
function cli()
{
document.location.href='http://www.elinks.cz';
}
function loader()
{
document.getElementById('c').addEventListener('click', cli);
}
</script>
</head>
<body onload="loader()">
<center>
<input type="button" id="c" value="ELinks homepage">
</center>
</body>
</html>

View File

@ -0,0 +1,21 @@
<html>
<head>
<script>
function cli()
{
alert("document.location.href='http://www.elinks.cz';");
}
function loader()
{
document.getElementById('c').addEventListener('click', function() { window.alert('aaaa'); });
document.getElementById('c').addEventListener('click', cli);
}
</script>
</head>
<body onload="loader()">
<center>
<button id="c" value="ELinks homepage">AAA</button>
</center>
</body>
</html>