Declare element types of lists.
[elinks/kon.git] / src / document / renderer.c
blob1873b881c56cb00f5c87fe07db58247323a051bd
1 /* HTML renderer */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <ctype.h>
8 #include <stdarg.h>
9 #include <stdlib.h>
10 #include <string.h>
12 #include "elinks.h"
14 #include "cache/cache.h"
15 #include "config/options.h"
16 #include "document/document.h"
17 #include "document/dom/renderer.h"
18 #include "document/html/frames.h"
19 #include "document/html/renderer.h"
20 #include "document/plain/renderer.h"
21 #include "document/renderer.h"
22 #include "document/view.h"
23 #include "ecmascript/ecmascript.h"
24 #include "encoding/encoding.h"
25 #include "intl/charsets.h"
26 #include "main/main.h"
27 #include "main/object.h"
28 #include "protocol/header.h"
29 #include "protocol/protocol.h"
30 #include "protocol/uri.h"
31 #include "session/location.h"
32 #include "session/session.h"
33 #include "terminal/terminal.h"
34 #include "terminal/window.h"
35 #include "util/error.h"
36 #include "util/memory.h"
37 #include "util/string.h"
38 #include "viewer/text/view.h"
39 #include "viewer/text/vs.h"
42 #ifdef CONFIG_ECMASCRIPT
43 /* XXX: This function is de facto obsolete, since we do not need to copy
44 * snippets around anymore (we process them in one go after the document is
45 * loaded; gradual processing was practically impossible because the snippets
46 * could reorder randomly during the loading - consider i.e.
47 * <body onLoad><script></body>: first just <body> is loaded, but then the
48 * rest of the document is loaded and <script> gets before <body>; do not even
49 * imagine the trouble with rewritten (through scripting hooks) documents;
50 * besides, implementing document.write() will be much simpler).
51 * But I want to take no risk by reworking that now. --pasky */
52 static void
53 add_snippets(struct ecmascript_interpreter *interpreter,
54 LIST_OF(struct string_list_item) *doc_snippets,
55 LIST_OF(struct string_list_item) *queued_snippets)
57 struct string_list_item *doc_current = doc_snippets->next;
59 #ifdef CONFIG_LEDS
60 if (list_empty(*queued_snippets) && interpreter->vs->doc_view->session)
61 unset_led_value(interpreter->vs->doc_view->session->status.ecmascript_led);
62 #endif
64 if (list_empty(*doc_snippets) || !get_opt_bool("ecmascript.enable"))
65 return;
67 /* We do this all only once per view_state now. */
68 if (!list_empty(*queued_snippets)) {
69 /* So if we already did it, we shouldn't need to do it again.
70 * This is the case of moving around in history - we have all
71 * what happenned recorded in the view_state and needn't bother
72 * again. */
73 #ifdef CONFIG_DEBUG
74 /* Hopefully. */
75 struct string_list_item *iterator = queued_snippets->next;
77 while (iterator != (struct string_list_item *) queued_snippets) {
78 if (doc_current == (struct string_list_item *) doc_snippets) {
79 INTERNAL("add_snippets(): doc_snippets shorter than queued_snippets!");
80 return;
82 #if 0
83 DBG("Comparing snippets\n%.*s\n###### vs #####\n%.*s\n #####",
84 iterator->string.length, iterator->string.source,
85 doc_current->string.length, doc_current->string.source);
86 #endif
87 assert(!strlcmp(iterator->string.source,
88 iterator->string.length,
89 doc_current->string.source,
90 doc_current->string.length));
92 doc_current = doc_current->next;
93 iterator = iterator->next;
95 #endif
96 return;
99 assert(doc_current);
100 for (; doc_current != (struct string_list_item *) doc_snippets;
101 doc_current = doc_current->next) {
102 add_to_string_list(queued_snippets, doc_current->string.source,
103 doc_current->string.length);
104 #if 0
105 DBG("Adding snippet\n%.*s\n #####",
106 doc_current->string.length,
107 doc_current->string.source);
108 #endif
112 static void
113 process_snippets(struct ecmascript_interpreter *interpreter,
114 LIST_OF(struct string_list_item) *snippets,
115 struct string_list_item **current)
117 if (!*current)
118 *current = snippets->next;
119 for (; *current != (struct string_list_item *) snippets;
120 (*current) = (*current)->next) {
121 struct string *string = &(*current)->string;
122 unsigned char *uristring;
123 struct uri *uri;
124 struct cache_entry *cached;
125 struct fragment *fragment;
127 if (string->length == 0)
128 continue;
130 if (*string->source != '^') {
131 /* Evaluate <script>code</script> snippet */
132 ecmascript_eval(interpreter, string, NULL);
133 continue;
136 /* Eval external <script src="reference"></script> snippet */
137 uristring = string->source + 1;
138 if (!*uristring) continue;
140 uri = get_uri(uristring, URI_BASE);
141 if (!uri) continue;
143 cached = get_redirected_cache_entry(uri);
144 done_uri(uri);
146 if (!cached) {
147 /* At this time (!gradual_rerendering), we should've
148 * already retrieved this though. So it must've been
149 * that it went away because unused and the cache was
150 * already too full. */
151 #if 0
152 /* Disabled because gradual rerendering can be triggered
153 * by numerous events other than a ecmascript reference
154 * completing like the original document and CSS. Problem
155 * is that we should never continue this loop but rather
156 * break out if that is the case. Somehow we need to
157 * be able to derive URI loading problems at this point
158 * or maybe remove reference snippets if they fail to load.
160 * This FIFO queue handling should be used for also CSS
161 * imports so it would be cool if it could be general
162 * enough for that. Using it for frames with the FIFOing
163 * disabled probably wouldn't hurt either.
165 * To top this thing off it would be nice if it also
166 * handled dependency tracking between references so that
167 * CSS documents will not disappear from the cache
168 * before all referencing HTML documents has been deleted
169 * from it.
171 * Reported as bug 533. */
172 /* Pasky's explanation: If we get the doc in a single
173 * shot, before calling draw_formatted() we didn't have
174 * anything additional queued for loading and the cache
175 * entry was already loaded, so we didn't get
176 * gradual_loading set. But then while parsing the
177 * document we got some external references and trying
178 * to process them right now. Boom.
180 * The obvious solution would be to always call
181 * draw_formatted() with gradual_loading in
182 * doc_loading_callback() and if we are sure the
183 * loading is really over, call it one more time
184 * without gradual_loading set. I'm not sure about
185 * the implications though so I won't do it before
186 * 0.10.0. --pasky */
187 ERROR("The script of %s was lost in too full a cache!",
188 uristring);
189 #endif
190 continue;
193 fragment = get_cache_fragment(cached);
194 if (fragment) {
195 struct string code = INIT_STRING(fragment->data, fragment->length);
197 ecmascript_eval(interpreter, &code, NULL);
201 #endif
203 static void
204 render_encoded_document(struct cache_entry *cached, struct document *document)
206 struct uri *uri = cached->uri;
207 enum stream_encoding encoding = ENCODING_NONE;
208 struct fragment *fragment = get_cache_fragment(cached);
209 struct string buffer = INIT_STRING("", 0);
211 /* Even empty documents have to be rendered so that info in the protocol
212 * header, such as refresh info, get processed. (bug 625) */
213 if (fragment) {
214 buffer.source = fragment->data;
215 buffer.length = fragment->length;
218 if (uri->protocol != PROTOCOL_FILE) {
219 unsigned char *extension = get_extension_from_uri(uri);
221 if (extension) {
222 encoding = guess_encoding(extension);
223 mem_free(extension);
226 if (encoding != ENCODING_NONE) {
227 int length = 0;
228 unsigned char *source;
230 source = decode_encoded_buffer(encoding, buffer.source,
231 buffer.length, &length);
232 if (source) {
233 buffer.source = source;
234 buffer.length = length;
235 } else {
236 encoding = ENCODING_NONE;
241 if (document->options.plain) {
242 #ifdef CONFIG_DOM
243 if (cached->content_type
244 && (!strcasecmp("text/html", cached->content_type)
245 || !strcasecmp("application/xhtml+xml", cached->content_type)
246 || !strcasecmp("application/docbook+xml", cached->content_type)
247 || !strcasecmp("application/rss+xml", cached->content_type)
248 || !strcasecmp("application/xbel+xml", cached->content_type)
249 || !strcasecmp("application/x-xbel", cached->content_type)
250 || !strcasecmp("application/xbel", cached->content_type)))
251 render_dom_document(cached, document, &buffer);
252 else
253 #endif
254 render_plain_document(cached, document, &buffer);
256 } else {
257 #ifdef CONFIG_DOM
258 if (cached->content_type
259 && (!strlcasecmp("application/rss+xml", 19, cached->content_type, -1)))
260 render_dom_document(cached, document, &buffer);
261 else
262 #endif
263 render_html_document(cached, document, &buffer);
266 if (encoding != ENCODING_NONE) {
267 done_string(&buffer);
271 void
272 render_document(struct view_state *vs, struct document_view *doc_view,
273 struct document_options *options)
275 unsigned char *name;
276 struct document *document;
277 struct cache_entry *cached;
279 assert(vs && doc_view && options);
280 if_assert_failed return;
282 #if 0
283 DBG("(Re%u)Rendering %s on doc_view %p [%s] while attaching it to %p",
284 options->gradual_rerendering, struri(vs->uri),
285 doc_view, doc_view->name, vs);
286 #endif
288 name = doc_view->name;
289 doc_view->name = NULL;
291 detach_formatted(doc_view);
293 doc_view->name = name;
294 doc_view->vs = vs;
295 doc_view->last_x = doc_view->last_y = -1;
297 #if 0
298 /* This is a nice idea, but doesn't always work: in particular when
299 * there's a frame name conflict. You loaded something to the vs'
300 * frame, but later something tried to get loaded to a frame with
301 * the same name and we got back this frame again, so we are now
302 * overriding the original document with a cuckoo. This assert()ion
303 * should be re-enabled when we start to get this right (which is
304 * very complex, but someone should rewrite the frames support
305 * anyway). --pasky */
306 assert(!vs->doc_view);
307 #else
308 if (vs->doc_view) {
309 /* It will be still detached, no worries - hopefully it still
310 * resides in ses->scrn_frames. */
311 assert(vs->doc_view->vs == vs);
312 vs->doc_view->used = 0; /* A bit risky, but... */
313 vs->doc_view->vs = NULL;
314 vs->doc_view = NULL;
315 #ifdef CONFIG_ECMASCRIPT
316 vs->ecmascript_fragile = 1; /* And is this good? ;-) */
317 #endif
319 #endif
320 vs->doc_view = doc_view;
322 cached = find_in_cache(vs->uri);
323 if (!cached) {
324 INTERNAL("document %s to format not found", struri(vs->uri));
325 return;
328 document = get_cached_document(cached, options);
329 if (document) {
330 doc_view->document = document;
331 } else {
332 document = init_document(cached, options);
333 if (!document) return;
334 doc_view->document = document;
336 shrink_memory(0);
338 render_encoded_document(cached, document);
339 sort_links(document);
340 if (!document->title) {
341 enum uri_component components;
343 if (document->uri->protocol == PROTOCOL_FILE) {
344 components = URI_PATH;
345 } else {
346 components = URI_PUBLIC;
349 document->title = get_uri_string(document->uri, components);
350 if (document->title) {
351 #ifdef CONFIG_UTF8
352 if (doc_view->document->options.utf8)
353 decode_uri(document->title);
354 else
355 #endif /* CONFIG_UTF8 */
356 decode_uri_for_display(document->title);
360 #ifdef CONFIG_CSS
361 document->css_magic = get_document_css_magic(document);
362 #endif
364 #ifdef CONFIG_ECMASCRIPT
365 if (!vs->ecmascript_fragile)
366 assert(vs->ecmascript);
367 if (!options->gradual_rerendering) {
368 /* We also reset the state if the underlying document changed
369 * from the last time we did the snippets. This may be
370 * triggered i.e. when redrawing a document which has been
371 * reloaded in a different tab meanwhile (OTOH we don't want
372 * to reset the state if we are redrawing a document we have
373 * already drawn before).
375 * (vs->ecmascript->onload_snippets_owner) check may be
376 * superfluous since we should always have
377 * vs->ecmascript_fragile set in those cases; that's why we
378 * don't ever bother to re-zero it if we are suddenly doing
379 * gradual rendering again.
381 * XXX: What happens if a document is still loading in the
382 * other tab when we press ^L here? */
383 if (vs->ecmascript_fragile
384 || (vs->ecmascript && vs->ecmascript->onload_snippets_owner
385 && document->id != vs->ecmascript->onload_snippets_owner))
386 ecmascript_reset_state(vs);
387 assert(vs->ecmascript);
388 vs->ecmascript->onload_snippets_owner = document->id;
390 /* Passing of the onload_snippets pointers gives *_snippets()
391 * some feeling of universality, shall we ever get any other
392 * snippets (?). */
393 add_snippets(vs->ecmascript,
394 &document->onload_snippets,
395 &vs->ecmascript->onload_snippets);
396 process_snippets(vs->ecmascript, &vs->ecmascript->onload_snippets,
397 &vs->ecmascript->current_onload_snippet);
399 #endif
401 /* If we do not care about the height and width of the document
402 * just use the setup values. */
404 copy_box(&doc_view->box, &document->options.box);
406 if (!document->options.needs_width)
407 doc_view->box.width = options->box.width;
409 if (!document->options.needs_height)
410 doc_view->box.height = options->box.height;
414 void
415 render_document_frames(struct session *ses, int no_cache)
417 struct document_options doc_opts;
418 struct document_view *doc_view;
419 struct document_view *current_doc_view = NULL;
420 struct view_state *vs = NULL;
422 if (!ses->doc_view) {
423 ses->doc_view = mem_calloc(1, sizeof(*ses->doc_view));
424 if (!ses->doc_view) return;
425 ses->doc_view->session = ses;
426 ses->doc_view->search_word = &ses->search_word;
429 if (have_location(ses)) vs = &cur_loc(ses)->vs;
431 init_document_options(&doc_opts);
433 set_box(&doc_opts.box, 0, 0,
434 ses->tab->term->width, ses->tab->term->height);
436 if (ses->status.show_title_bar) {
437 doc_opts.box.y++;
438 doc_opts.box.height--;
440 if (ses->status.show_status_bar) doc_opts.box.height--;
441 if (ses->status.show_tabs_bar) {
442 doc_opts.box.height--;
443 if (ses->status.show_tabs_bar_at_top) doc_opts.box.y++;
446 doc_opts.color_mode = get_opt_int_tree(ses->tab->term->spec, "colors");
447 if (!get_opt_bool_tree(ses->tab->term->spec, "underline"))
448 doc_opts.color_flags |= COLOR_ENHANCE_UNDERLINE;
450 doc_opts.cp = get_opt_codepage_tree(ses->tab->term->spec, "charset");
451 doc_opts.no_cache = no_cache & 1;
452 doc_opts.gradual_rerendering = !!(no_cache & 2);
454 if (vs) {
455 if (vs->plain < 0) vs->plain = 0;
456 doc_opts.plain = vs->plain;
457 doc_opts.wrap = vs->wrap;
458 } else {
459 doc_opts.plain = 1;
462 foreach (doc_view, ses->scrn_frames) doc_view->used = 0;
464 if (vs) render_document(vs, ses->doc_view, &doc_opts);
466 if (document_has_frames(ses->doc_view->document)) {
467 current_doc_view = current_frame(ses);
468 format_frames(ses, ses->doc_view->document->frame_desc, &doc_opts, 0);
471 foreach (doc_view, ses->scrn_frames) {
472 struct document_view *prev_doc_view = doc_view->prev;
474 if (doc_view->used) continue;
476 detach_formatted(doc_view);
477 del_from_list(doc_view);
478 mem_free(doc_view);
479 doc_view = prev_doc_view;
482 if (current_doc_view) {
483 int n = 0;
485 foreach (doc_view, ses->scrn_frames) {
486 if (document_has_frames(doc_view->document)) continue;
487 if (doc_view == current_doc_view) {
488 cur_loc(ses)->vs.current_link = n;
489 break;
491 n++;
496 static int
497 comp_links(struct link *l1, struct link *l2)
499 assert(l1 && l2);
500 if_assert_failed return 0;
501 return (l1->number - l2->number);
504 void
505 sort_links(struct document *document)
507 int i;
509 assert(document);
510 if_assert_failed return;
511 if (!document->nlinks) return;
513 if (document->links_sorted) return;
514 assert(document->links);
515 if_assert_failed return;
517 qsort(document->links, document->nlinks, sizeof(*document->links),
518 (void *) comp_links);
520 if (!document->height) return;
522 mem_free_if(document->lines1);
523 document->lines1 = mem_calloc(document->height, sizeof(*document->lines1));
524 mem_free_if(document->lines2);
525 if (!document->lines1) return;
526 document->lines2 = mem_calloc(document->height, sizeof(*document->lines2));
527 if (!document->lines2) {
528 mem_free(document->lines1);
529 return;
532 for (i = 0; i < document->nlinks; i++) {
533 struct link *link = &document->links[i];
534 int p, q, j;
536 if (!link->npoints) {
537 done_link_members(link);
538 memmove(link, link + 1,
539 (document->nlinks - i - 1) * sizeof(*link));
540 document->nlinks--;
541 i--;
542 continue;
544 p = link->points[0].y;
545 q = link->points[link->npoints - 1].y;
546 if (p > q) j = p, p = q, q = j;
547 for (j = p; j <= q; j++) {
548 assertm(j < document->height, "link out of screen");
549 if_assert_failed continue;
550 document->lines2[j] = &document->links[i];
551 if (!document->lines1[j])
552 document->lines1[j] = &document->links[i];
555 document->links_sorted = 1;
558 struct conv_table *
559 get_convert_table(unsigned char *head, int to_cp,
560 int default_cp, int *from_cp,
561 enum cp_status *cp_status, int ignore_server_cp)
563 unsigned char *part = head;
564 int cp_index = -1;
566 assert(head);
567 if_assert_failed return NULL;
569 if (ignore_server_cp) {
570 if (cp_status) *cp_status = CP_STATUS_IGNORED;
571 if (from_cp) *from_cp = default_cp;
572 return get_translation_table(default_cp, to_cp);
575 while (cp_index == -1) {
576 unsigned char *ct_charset;
577 unsigned char *meta;
578 unsigned char *a = parse_header(part, "Content-Type", &part);
580 if (!a) break;
581 /* Content type info from document meta header.
582 * scan_http_equiv() appends the meta stuff to the protocol header before
583 * this function is called. Last Content-Type header field is used. */
585 while ((meta = parse_header(part, "Content-Type", &part))) {
586 mem_free_set(&a, meta);
589 parse_header_param(a, "charset", &ct_charset);
590 if (ct_charset) {
591 cp_index = get_cp_index(ct_charset);
592 mem_free(ct_charset);
594 mem_free(a);
597 if (cp_index == -1) {
598 unsigned char *a = parse_header(head, "Content-Charset", NULL);
600 if (a) {
601 cp_index = get_cp_index(a);
602 mem_free(a);
606 if (cp_index == -1) {
607 unsigned char *a = parse_header(head, "Charset", NULL);
609 if (a) {
610 cp_index = get_cp_index(a);
611 mem_free(a);
615 if (cp_index == -1) {
616 cp_index = default_cp;
617 if (cp_status) *cp_status = CP_STATUS_ASSUMED;
618 } else {
619 if (cp_status) *cp_status = CP_STATUS_SERVER;
622 if (from_cp) *from_cp = cp_index;
624 return get_translation_table(cp_index, to_cp);