1 /**
2  * vimb - a webkit based vim like browser.
3  *
4  * Copyright (C) 2012-2018 Daniel Carl
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see http://www.gnu.org/licenses/.
18  */
19 
20 #include <glib.h>
21 #include <webkitdom/webkitdom.h>
22 
23 #include "ext-main.h"
24 #include "ext-dom.h"
25 
26 static gboolean is_element_visible(WebKitDOMHTMLElement *element);
27 
28 
29 /**
30  * Checks if given dom element is an editable element.
31  */
ext_dom_is_editable(WebKitDOMElement * element)32 gboolean ext_dom_is_editable(WebKitDOMElement *element)
33 {
34     char *type;
35     gboolean result = FALSE;
36 
37     if (!element) {
38         return FALSE;
39     }
40 
41     /* element is editable if it's a text area or input with no type, text or
42      * password */
43     if (webkit_dom_html_element_get_is_content_editable(WEBKIT_DOM_HTML_ELEMENT(element))
44             || WEBKIT_DOM_IS_HTML_TEXT_AREA_ELEMENT(element)) {
45         return TRUE;
46     }
47 
48     if (WEBKIT_DOM_IS_HTML_INPUT_ELEMENT(element)) {
49         type = webkit_dom_html_input_element_get_input_type(WEBKIT_DOM_HTML_INPUT_ELEMENT(element));
50         /* Input element without type attribute are rendered and behave like
51          * type = text and there are a lot of pages in the wild using input
52          * field without type attribute. */
53         if (!*type
54             || !g_ascii_strcasecmp(type, "text")
55             || !g_ascii_strcasecmp(type, "password")
56             || !g_ascii_strcasecmp(type, "color")
57             || !g_ascii_strcasecmp(type, "date")
58             || !g_ascii_strcasecmp(type, "datetime")
59             || !g_ascii_strcasecmp(type, "datetime-local")
60             || !g_ascii_strcasecmp(type, "email")
61             || !g_ascii_strcasecmp(type, "month")
62             || !g_ascii_strcasecmp(type, "number")
63             || !g_ascii_strcasecmp(type, "search")
64             || !g_ascii_strcasecmp(type, "tel")
65             || !g_ascii_strcasecmp(type, "time")
66             || !g_ascii_strcasecmp(type, "url")
67             || !g_ascii_strcasecmp(type, "week"))
68         {
69             result = TRUE;
70         }
71 
72         g_free(type);
73     }
74 
75     return result;
76 }
77 
78 /**
79  * Find the first editable element and set the focus on it and enter input
80  * mode.
81  * Returns true if there was an editable element focused.
82  */
ext_dom_focus_input(WebKitDOMDocument * doc)83 gboolean ext_dom_focus_input(WebKitDOMDocument *doc)
84 {
85     WebKitDOMNode *html, *node;
86     WebKitDOMHTMLCollection *collection;
87     WebKitDOMXPathNSResolver *resolver;
88     WebKitDOMXPathResult* result;
89     WebKitDOMDocument *frame_doc;
90     guint i, len;
91 
92     collection = webkit_dom_document_get_elements_by_tag_name_as_html_collection(doc, "html");
93     if (!collection) {
94         return FALSE;
95     }
96 
97     html = webkit_dom_html_collection_item(collection, 0);
98     g_object_unref(collection);
99 
100     resolver = webkit_dom_document_create_ns_resolver(doc, html);
101     if (!resolver) {
102         return FALSE;
103     }
104 
105     /* Use translate to match xpath expression case insensitive so that also
106      * intput filed of type="TEXT" are matched. */
107     result = webkit_dom_document_evaluate(
108         doc, "//input[not(@type) "
109         "or translate(@type,'ETX','etx')='text' "
110         "or translate(@type,'ADOPRSW','adoprsw')='password' "
111         "or translate(@type,'CLOR','clor')='color' "
112         "or translate(@type,'ADET','adet')='date' "
113         "or translate(@type,'ADEIMT','adeimt')='datetime' "
114         "or translate(@type,'ACDEILMOT','acdeilmot')='datetime-local' "
115         "or translate(@type,'AEILM','aeilm')='email' "
116         "or translate(@type,'HMNOT','hmnot')='month' "
117         "or translate(@type,'BEMNRU','bemnru')='number' "
118         "or translate(@type,'ACEHRS','acehrs')='search' "
119         "or translate(@type,'ELT','elt')='tel' "
120         "or translate(@type,'EIMT','eimt')='time' "
121         "or translate(@type,'LRU','lru')='url' "
122         "or translate(@type,'EKW','ekw')='week' "
123         "]|//textarea",
124         html, resolver, 5, NULL, NULL
125     );
126     if (!result) {
127         return FALSE;
128     }
129     while ((node = webkit_dom_xpath_result_iterate_next(result, NULL))) {
130         if (is_element_visible(WEBKIT_DOM_HTML_ELEMENT(node))) {
131             webkit_dom_element_focus(WEBKIT_DOM_ELEMENT(node));
132             return TRUE;
133         }
134     }
135 
136     /* Look for editable elements in frames too. */
137     collection = webkit_dom_document_get_elements_by_tag_name_as_html_collection(doc, "iframe");
138     len        = webkit_dom_html_collection_get_length(collection);
139 
140     for (i = 0; i < len; i++) {
141         node      = webkit_dom_html_collection_item(collection, i);
142         frame_doc = webkit_dom_html_iframe_element_get_content_document(WEBKIT_DOM_HTML_IFRAME_ELEMENT(node));
143         /* Stop on first frame with focused element. */
144         if (ext_dom_focus_input(frame_doc)) {
145             g_object_unref(collection);
146             return TRUE;
147         }
148     }
149     g_object_unref(collection);
150 
151     return FALSE;
152 }
153 
154 /**
155  * Retrieves the content of given editable element.
156  * Not that the returned value must be freed.
157  */
ext_dom_editable_get_value(WebKitDOMElement * element)158 char *ext_dom_editable_get_value(WebKitDOMElement *element)
159 {
160     char *value = NULL;
161 
162     if ((webkit_dom_html_element_get_is_content_editable(WEBKIT_DOM_HTML_ELEMENT(element)))) {
163         value = webkit_dom_html_element_get_inner_text(WEBKIT_DOM_HTML_ELEMENT(element));
164     } else if (WEBKIT_DOM_IS_HTML_INPUT_ELEMENT(WEBKIT_DOM_HTML_INPUT_ELEMENT(element))) {
165         value = webkit_dom_html_input_element_get_value(WEBKIT_DOM_HTML_INPUT_ELEMENT(element));
166     } else {
167         value = webkit_dom_html_text_area_element_get_value(WEBKIT_DOM_HTML_TEXT_AREA_ELEMENT(element));
168     }
169 
170     return value;
171 }
172 
ext_dom_lock_input(WebKitDOMDocument * parent,char * element_id)173 void ext_dom_lock_input(WebKitDOMDocument *parent, char *element_id)
174 {
175     WebKitDOMElement *elem;
176 
177     elem = webkit_dom_document_get_element_by_id(parent, element_id);
178     if (elem != NULL) {
179         webkit_dom_element_set_attribute(elem, "disabled", "true", NULL);
180     }
181 }
182 
ext_dom_unlock_input(WebKitDOMDocument * parent,char * element_id)183 void ext_dom_unlock_input(WebKitDOMDocument *parent, char *element_id)
184 {
185     WebKitDOMElement *elem;
186 
187     elem = webkit_dom_document_get_element_by_id(parent, element_id);
188     if (elem != NULL) {
189         webkit_dom_element_remove_attribute(elem, "disabled");
190         webkit_dom_element_focus(elem);
191     }
192 }
193 
194 /**
195  * Indicates if the give nelement is visible.
196  */
is_element_visible(WebKitDOMHTMLElement * element)197 static gboolean is_element_visible(WebKitDOMHTMLElement *element)
198 {
199     return TRUE;
200 }
201 
202