From c7aefa2cd7ef9bae68773df9f338da4b44a76d73 Mon Sep 17 00:00:00 2001 From: Jorge Arellano Cid Date: Tue, 26 May 2015 11:29:21 -0300 Subject: Fix view-source dpi to handle null characters correctly Although not allowed in text contexts, null characters should not stop/halt/fail dpi protocol, thus the patch. Test Example. Display a file with these contents: null padding^@^@ (two trailing null characters) and view source for it. Note that dillo will not _display_ the file completely correct, it will eat a char after each null, but this is not a problem in dpi nor dpip but in rendering, the cache gets it right. Adding code to correctly _display_ these anomalous pages is probably not worth the effort though. --- doc/Dpid.txt | 5 +++-- dpi/vsource.c | 56 +++++++++++++++++++++++++++++++------------------------- dpip/dpip.c | 21 ++++++++++++++++++--- dpip/dpip.h | 1 + 4 files changed, 53 insertions(+), 30 deletions(-) diff --git a/doc/Dpid.txt b/doc/Dpid.txt index 82b81311..6c418f57 100644 --- a/doc/Dpid.txt +++ b/doc/Dpid.txt @@ -285,9 +285,10 @@ commented code in hello.c and start making changes! Debugging a dpi --------------- - The simplest way is to add printf() feedback using the MSG* + The simplest way is to add printf-like feedback using the MSG* macros. You can start the dpid by hand on a terminal to force -messages to go there. +messages to go there. Filter dpis use sdterr and server dpis +stdout. Sometimes more complex dpis need more than MSG*. In this case you can use gdb like this. diff --git a/dpi/vsource.c b/dpi/vsource.c index 2f1129cb..c28e7b49 100644 --- a/dpi/vsource.c +++ b/dpi/vsource.c @@ -3,7 +3,7 @@ * * This server is an example. Play with it and modify to your taste. * - * Copyright 2010 Jorge Arellano Cid + * Copyright 2010-2015 Jorge Arellano Cid * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,7 +25,7 @@ * Debugging macros */ #define _MSG(...) -#define MSG(...) printf("[vsource dpi]: " __VA_ARGS__) +#define MSG(...) fprintf(stderr, "[vsource dpi]: " __VA_ARGS__) /*---------------------------------------------------------------------------*/ @@ -42,38 +42,41 @@ void send_dpip_tag(Dsh *sh, char *dpip_tag) /* * Send source as plain text + * (handles embedded null chars correctly). */ void send_plain_text(Dsh *sh, int data_size) { - int bytes_read = 0; - char *src_str; + char *token; + int bytes_read = 0, token_size; /* Send HTTP header for plain text MIME type */ a_Dpip_dsh_write_str(sh, 0, "Content-type: text/plain\n\n"); while (bytes_read < data_size && - (src_str = a_Dpip_dsh_read_token(sh, 1))) { - bytes_read += strlen(src_str); - a_Dpip_dsh_write_str(sh, 1, src_str); - dFree(src_str); + (token = a_Dpip_dsh_read_token2(sh, 1, &token_size))) { + bytes_read += token_size; + _MSG("data_size=%d bytes_read=%d\n", data_size, bytes_read); + a_Dpip_dsh_write(sh, 1, token, token_size); + dFree(token); } } /* * Send source as plain text with line numbers + * (handles embedded null chars correctly). */ void send_numbered_text(Dsh *sh, int data_size) { - int bytes_read = 0, line = 1; - char *p, *q, *src_str, line_str[32]; + int bytes_read = 0, line = 1, token_size = 0; + char *p, *q, *token, line_str[32]; /* Send HTTP header for plain text MIME type */ a_Dpip_dsh_write_str(sh, 0, "Content-type: text/plain\n\n"); while (bytes_read < data_size && - (src_str = a_Dpip_dsh_read_token(sh, 1))) { - bytes_read += strlen(src_str); - p = q = src_str; + (token = a_Dpip_dsh_read_token2(sh, 1, &token_size))) { + bytes_read += token_size; + p = q = token; while (*p) { snprintf(line_str, 32, "%2d: ", line); @@ -84,28 +87,30 @@ void send_numbered_text(Dsh *sh, int data_size) ++p; ++line; } else { - a_Dpip_dsh_write_str(sh, 1, q); + /* send all the rest */ + a_Dpip_dsh_write(sh, 1, q, token_size - (q - token)); break; } q = ++p; } - dFree(src_str); + dFree(token); } } /* * Send source as html text with line numbers + * (handles embedded null chars correctly). */ void send_html_text(Dsh *sh, const char *url, int data_size) { - int bytes_read = 0, old_line = 0, line = 1; - char *p, *q, *src_str, line_str[128]; + int bytes_read = 0, old_line = 0, line = 1, token_size = 0; + char *p, *q, *token, line_str[128]; if (dStrnAsciiCasecmp(url, "dpi:", 4) == 0 && strncmp(url+4, "/vsource/:", 10) == 0) url += 14; - /* Send HTTP header for plain text MIME type */ + /* Send HTTP header for html text MIME type */ a_Dpip_dsh_write_str(sh, 0, "Content-type: text/html\n\n"); a_Dpip_dsh_write_str(sh, 0, DOCTYPE); @@ -119,9 +124,9 @@ void send_html_text(Dsh *sh, const char *url, int data_size) "\n\n", url); while (bytes_read < data_size && - (src_str = a_Dpip_dsh_read_token(sh, 1))) { - bytes_read += strlen(src_str); - p = q = src_str; + (token = a_Dpip_dsh_read_token2(sh, 1, &token_size))) { + bytes_read += token_size; + p = q = token; while (*p) { if (line > old_line) { @@ -143,13 +148,14 @@ void send_html_text(Dsh *sh, const char *url, int data_size) a_Dpip_dsh_write(sh, 0, q, p - q); a_Dpip_dsh_write_str(sh, 0, (*p == '<') ? "<" : "&"); } - } else { - a_Dpip_dsh_write_str(sh, 1, q); + } else { + /* send all the rest */ + a_Dpip_dsh_write(sh, 1, q, token_size - (q - token)); break; } q = ++p; } - dFree(src_str); + dFree(token); } if (data_size > 0) @@ -194,7 +200,7 @@ int main(void) * asking from us. a_Dpip_dsh_read_token() will block and return * a full dpip token or null on error (it's commented in dpip.c) */ dpip_tag = a_Dpip_dsh_read_token(sh, 1); - MSG("tag = [%s]\n", dpip_tag); + _MSG("tag = [%s]\n", dpip_tag); /* Now that we have the dpip_tag, let's isolate the command and url */ cmd = a_Dpip_get_attr(dpip_tag, "cmd"); diff --git a/dpip/dpip.c b/dpip/dpip.c index f4ce1bf0..2906ba2a 100644 --- a/dpip/dpip.c +++ b/dpip/dpip.c @@ -1,7 +1,7 @@ /* * File: dpip.c * - * Copyright 2005-2007 Jorge Arellano Cid + * Copyright 2005-2015 Jorge Arellano Cid * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -427,11 +427,13 @@ static void Dpip_dsh_read(Dsh *dsh, int blocking) /* * Return a newlly allocated string with the next dpip token in the socket. - * Return value: token string on success, NULL otherwise + * Return value: token string and length on success, NULL otherwise. + * (useful for handling null characters in the data stream) */ -char *a_Dpip_dsh_read_token(Dsh *dsh, int blocking) +char *a_Dpip_dsh_read_token2(Dsh *dsh, int blocking, int *DataSize) { char *p, *ret = NULL; + *DataSize = 0; /* Read all available data without blocking */ Dpip_dsh_read(dsh, 0); @@ -462,6 +464,7 @@ char *a_Dpip_dsh_read_token(Dsh *dsh, int blocking) /* return a full tag */ if ((p = strstr(dsh->rdbuf->str, DPIP_TAG_END))) { ret = dStrndup(dsh->rdbuf->str, p - dsh->rdbuf->str + 3); + *DataSize = p - dsh->rdbuf->str + 3; dStr_erase(dsh->rdbuf, 0, p - dsh->rdbuf->str + 3); if (strstr(ret, DPIP_MODE_SWITCH_TAG)) dsh->mode |= DPIP_LAST_TAG; @@ -470,6 +473,7 @@ char *a_Dpip_dsh_read_token(Dsh *dsh, int blocking) /* raw mode, return what we have "as is" */ if (dsh->rdbuf->len > 0) { ret = dStrndup(dsh->rdbuf->str, dsh->rdbuf->len); + *DataSize = dsh->rdbuf->len; dStr_truncate(dsh->rdbuf, 0); } } @@ -477,6 +481,17 @@ char *a_Dpip_dsh_read_token(Dsh *dsh, int blocking) return ret; } +/* + * Return a newlly allocated string with the next dpip token in the socket. + * Return value: token string on success, NULL otherwise + */ +char *a_Dpip_dsh_read_token(Dsh *dsh, int blocking) +{ + int token_size; + + return a_Dpip_dsh_read_token2(dsh, blocking, &token_size); +} + /* * Close this socket for reading and writing. * (flush pending data) diff --git a/dpip/dpip.h b/dpip/dpip.h index 1a1846df..a63eb658 100644 --- a/dpip/dpip.h +++ b/dpip/dpip.h @@ -70,6 +70,7 @@ int a_Dpip_dsh_write_str(Dsh *dsh, int flush, const char *str); int a_Dpip_dsh_tryflush(Dsh *dsh); int a_Dpip_dsh_trywrite(Dsh *dsh, const char *Data, int DataSize); char *a_Dpip_dsh_read_token(Dsh *dsh, int blocking); +char *a_Dpip_dsh_read_token2(Dsh *dsh, int blocking, int *DataSize); void a_Dpip_dsh_close(Dsh *dsh); void a_Dpip_dsh_free(Dsh *dsh); -- cgit v1.2.3 From 3240d7de54558ba7c91abe346f2420377c8ce685 Mon Sep 17 00:00:00 2001 From: corvid Date: Tue, 26 May 2015 18:14:19 +0000 Subject: ChangeLog --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index b50d6268..4ca7d028 100644 --- a/ChangeLog +++ b/ChangeLog @@ -25,6 +25,7 @@ dillo-3.1 [not released yet] Patches: Sebastian Geerken +- Image buffer/cache improvements. - Fix for segfault when there's no dpid and view source is requested. + - Fix view-source dpi to handle null characters correctly. Patches: Jorge Arellano Cid +- Crosscompile/buildroot-friendly fltk-config test. Patch: Peter Seiderer -- cgit v1.2.3 From af2f4800e97d5f9ae3a43062cdac0425362cccba Mon Sep 17 00:00:00 2001 From: Jorge Arellano Cid Date: Wed, 27 May 2015 11:07:04 -0300 Subject: Made view-source dpi use CSS formatting (it's shorter and cleaner) BTW, is there a point in using a monospaced font? Besides it looks like code printing (which is good), a proportional-spaced font may be easier to read. --- ChangeLog | 1 + dpi/vsource.c | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4ca7d028..76f4cb67 100644 --- a/ChangeLog +++ b/ChangeLog @@ -26,6 +26,7 @@ dillo-3.1 [not released yet] +- Image buffer/cache improvements. - Fix for segfault when there's no dpid and view source is requested. - Fix view-source dpi to handle null characters correctly. + - Made view-source dpi use CSS formatting (it's shorter and cleaner). Patches: Jorge Arellano Cid +- Crosscompile/buildroot-friendly fltk-config test. Patch: Peter Seiderer diff --git a/dpi/vsource.c b/dpi/vsource.c index c28e7b49..9d5694b5 100644 --- a/dpi/vsource.c +++ b/dpi/vsource.c @@ -118,7 +118,10 @@ void send_html_text(Dsh *sh, const char *url, int data_size) "\n" "\n" "Source for %s\n" - "\n" "\n" "\n
\n", url); @@ -131,10 +134,9 @@ void send_html_text(Dsh *sh, const char *url, int data_size) while (*p) { if (line > old_line) { snprintf(line_str, 128, - "%s
%d%s
",
-                     (line > 1) ? "
" : "", - (line & 1) ? "#B87333" : "#DD7F32", line, - (line == 1 || (line % 10) == 0) ? "  " : ""); + "
%d%s", + (line & 1) ? "r1" : "r2", line, + (line == 1 || (line % 10) == 0) ? " " : ""); a_Dpip_dsh_write_str(sh, 0, line_str); old_line = line; } @@ -158,8 +160,6 @@ void send_html_text(Dsh *sh, const char *url, int data_size) dFree(token); } - if (data_size > 0) - a_Dpip_dsh_write_str(sh, 0, ""); a_Dpip_dsh_write_str(sh, 1, "
"); } -- cgit v1.2.3 From ea14c266b84296761354f63d746a289db0bb4918 Mon Sep 17 00:00:00 2001 From: corvid Date: Thu, 28 May 2015 16:58:39 +0000 Subject: make http_max_conns truly per server/proxy rather than host And separate http from https for safety while we're at it. We were checking this where we needed to, but it would be easy to forget about in the future. Not that very much happens when you try http://example.com:443 or https://example.com:80, but I'm being careful nevertheless. --- src/IO/http.c | 156 ++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 85 insertions(+), 71 deletions(-) diff --git a/src/IO/http.c b/src/IO/http.c index e5c459ee..22b2eaa6 100644 --- a/src/IO/http.c +++ b/src/IO/http.c @@ -57,13 +57,13 @@ static const int HTTP_SOCKET_SSL = 0x8; /* 'web' is just a reference (no need to deallocate it here). */ typedef struct { int SockFD; - uint_t connect_port; uint_t flags; DilloWeb *web; /* reference to client's web structure */ DilloUrl *url; Dlist *addr_list; /* Holds the DNS answer */ ChainLink *Info; /* Used for CCC asynchronous operations */ - char *connected_to; /* Used for per-host connection limit */ + char *connected_to; /* Used for per-server connection limit */ + uint_t connect_port; Dstr *https_proxy_reply; } SocketData_t; @@ -72,19 +72,22 @@ typedef struct { */ typedef struct { char *host; + uint_t port; + bool_t https; + int active_conns; Dlist *queue; -} HostConnection_t; +} Server_t; typedef struct { int fd; int skey; } FdMapEntry_t; -static void Http_socket_enqueue(HostConnection_t *hc, SocketData_t* sock); -static HostConnection_t *Http_host_connection_get(const char *host); -static void Http_host_connection_remove(HostConnection_t *hc); -static void Http_connect_socket(ChainLink *Info, HostConnection_t *hc); +static void Http_socket_enqueue(Server_t *srv, SocketData_t* sock); +static Server_t *Http_server_get(const char *host, uint_t port, bool_t https); +static void Http_server_remove(Server_t *srv); +static void Http_connect_socket(ChainLink *Info); static char *Http_get_connect_str(const DilloUrl *url); static void Http_send_query(SocketData_t *S); static void Http_socket_free(int SKey); @@ -97,7 +100,7 @@ static Klist_t *ValidSocks = NULL; /* Active sockets list. It holds pointers to static DilloUrl *HTTP_Proxy = NULL; static char *HTTP_Proxy_Auth_base64 = NULL; static char *HTTP_Language_hdr = NULL; -static Dlist *host_connections; +static Dlist *servers; /* TODO: If fd_map will stick around in its present form (FDs and SocketData_t) * then consider whether having both this and ValidSocks is necessary. @@ -127,7 +130,7 @@ int a_Http_init(void) HTTP_Proxy_Auth_base64 = a_Misc_encode_base64(prefs.http_proxyuser); */ - host_connections = dList_new(5); + servers = dList_new(5); fd_map = dList_new(20); return 0; @@ -228,23 +231,24 @@ void a_Http_connect_done(int fd, bool_t success) } } -static void Http_socket_activate(HostConnection_t *hc, SocketData_t *sd) +static void Http_socket_activate(Server_t *srv, SocketData_t *sd) { - dList_remove(hc->queue, sd); + dList_remove(srv->queue, sd); sd->flags &= ~HTTP_SOCKET_QUEUED; - hc->active_conns++; - sd->connected_to = hc->host; + srv->active_conns++; + sd->connected_to = srv->host; } -static void Http_connect_queued_sockets(HostConnection_t *hc) +static void Http_connect_queued_sockets(Server_t *srv) { SocketData_t *sd; int i; for (i = 0; - i < dList_length(hc->queue) && hc->active_conns < prefs.http_max_conns; + (i < dList_length(srv->queue) && + srv->active_conns < prefs.http_max_conns); i++) { - sd = dList_nth_data(hc->queue, i); + sd = dList_nth_data(srv->queue, i); if (!(sd->flags & HTTP_SOCKET_TO_BE_FREED)) { int connect_ready = SSL_CONNECT_READY; @@ -258,18 +262,19 @@ static void Http_connect_queued_sockets(HostConnection_t *hc) Http_socket_free(SKey); } else if (connect_ready == SSL_CONNECT_READY) { i--; - Http_socket_activate(hc, sd); - Http_connect_socket(sd->Info, hc); + Http_socket_activate(srv, sd); + Http_connect_socket(sd->Info); } } if (sd->flags & HTTP_SOCKET_TO_BE_FREED) { - dList_remove(hc->queue, sd); + dList_remove(srv->queue, sd); dFree(sd); i--; } } - _MSG("Queue %s len %d\n", hc->host, dList_length(hc->queue)); + _MSG("Queue http%s://%s:%u len %d\n", srv->https ? "s" : "", srv->host, + srv->port, dList_length(srv->queue)); } /* @@ -294,11 +299,12 @@ static void Http_socket_free(int SKey) if (S->connected_to) { a_Ssl_close_by_fd(S->SockFD); - HostConnection_t *hc = Http_host_connection_get(S->connected_to); - hc->active_conns--; - Http_connect_queued_sockets(hc); - if (hc->active_conns == 0) - Http_host_connection_remove(hc); + Server_t *srv = Http_server_get(S->connected_to, S->connect_port, + (S->flags & HTTP_SOCKET_SSL)); + srv->active_conns--; + Http_connect_queued_sockets(srv); + if (srv->active_conns == 0) + Http_server_remove(srv); } a_Url_free(S->url); dFree(S); @@ -504,7 +510,7 @@ static void Http_connect_ssl(ChainLink *info) * This function is called after the DNS succeeds in solving a hostname. * Task: Finish socket setup and start connecting the socket. */ -static void Http_connect_socket(ChainLink *Info, HostConnection_t *hc) +static void Http_connect_socket(ChainLink *Info) { int i, status; SocketData_t *S; @@ -542,7 +548,7 @@ static void Http_connect_socket(ChainLink *Info, HostConnection_t *hc) sin->sin_port = htons(S->connect_port); memcpy(&sin->sin_addr, dh->data, (size_t)dh->alen); if (a_Web_valid(S->web) && (S->web->flags & WEB_RootUrl)) - MSG("Connecting to %s:%d\n", inet_ntoa(sin->sin_addr), + MSG("Connecting to %s:%u\n", inet_ntoa(sin->sin_addr), S->connect_port); break; } @@ -557,7 +563,7 @@ static void Http_connect_socket(ChainLink *Info, HostConnection_t *hc) memcpy(&sin6->sin6_addr, dh->data, dh->alen); inet_ntop(dh->af, dh->data, buf, sizeof(buf)); if (a_Web_valid(S->web) && (S->web->flags & WEB_RootUrl)) - MSG("Connecting to %s:%d\n", buf, S->connect_port); + MSG("Connecting to %s:%u\n", buf, S->connect_port); break; } #endif @@ -658,7 +664,7 @@ static void Http_dns_cb(int Status, Dlist *addr_list, void *data) int SKey = VOIDP2INT(data); bool_t clean_up = TRUE; SocketData_t *S; - HostConnection_t *hc; + Server_t *srv; S = a_Klist_get_data(ValidSocks, SKey); if (S) { @@ -670,9 +676,10 @@ static void Http_dns_cb(int Status, Dlist *addr_list, void *data) /* Successful DNS answer; save the IP */ S->addr_list = addr_list; clean_up = FALSE; - hc = Http_host_connection_get(host); - Http_socket_enqueue(hc, S); - Http_connect_queued_sockets(hc); + srv = Http_server_get(host, S->connect_port, + (S->flags & HTTP_SOCKET_SSL)); + Http_socket_enqueue(srv, S); + Http_connect_queued_sockets(srv); } else { /* DNS wasn't able to resolve the hostname */ MSG_BW(S->web, 0, "ERROR: DNS can't resolve %s", host); @@ -734,15 +741,17 @@ static int Http_get(ChainLink *Info, void *Data1) /* * Can the old socket's fd be reused for the new socket? * - * NOTE: old and new must come from the same HostConnection_t. + * NOTE: old and new must come from the same Server_t. * This is not built to accept arbitrary sockets. */ static bool_t Http_socket_reuse_compatible(SocketData_t *old, SocketData_t *new) { + /* + * If we are using SSL through a proxy, we need to ensure that old and new + * are going through to the same host:port. + */ if (a_Web_valid(new->web) && - old->connect_port == new->connect_port && - ((old->flags & HTTP_SOCKET_SSL) == (new->flags & HTTP_SOCKET_SSL)) && ((old->flags & HTTP_SOCKET_SSL) == 0 || (old->flags & HTTP_SOCKET_USE_PROXY) == 0 || ((URL_PORT(old->url) == URL_PORT(new->url)) && @@ -760,11 +769,13 @@ static void Http_socket_reuse(int SKey) SocketData_t *new_sd, *old_sd = a_Klist_get_data(ValidSocks, SKey); if (old_sd) { - HostConnection_t *hc = Http_host_connection_get(old_sd->connected_to); - int i, n = dList_length(hc->queue); + Server_t *srv = Http_server_get(old_sd->connected_to, + old_sd->connect_port, + (old_sd->flags & HTTP_SOCKET_SSL)); + int i, n = dList_length(srv->queue); for (i = 0; i < n; i++) { - new_sd = dList_nth_data(hc->queue, i); + new_sd = dList_nth_data(srv->queue, i); if (!(new_sd->flags & HTTP_SOCKET_TO_BE_FREED) && Http_socket_reuse_compatible(old_sd, new_sd)) { @@ -773,11 +784,11 @@ static void Http_socket_reuse(int SKey) new_sd->SockFD = old_sd->SockFD; old_sd->connected_to = NULL; - hc->active_conns--; + srv->active_conns--; Http_socket_free(SKey); MSG("Reusing fd %d for %s\n", new_sd->SockFD,URL_STR(new_sd->url)); - Http_socket_activate(hc, new_sd); + Http_socket_activate(srv, new_sd); Http_fd_map_add_entry(new_sd); a_Http_connect_done(new_sd->SockFD, success); return; @@ -935,68 +946,71 @@ void a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info, * Add socket data to the queue. Pages/stylesheets/etc. have higher priority * than images. */ -static void Http_socket_enqueue(HostConnection_t *hc, SocketData_t* sock) +static void Http_socket_enqueue(Server_t *srv, SocketData_t* sock) { sock->flags |= HTTP_SOCKET_QUEUED; if ((sock->web->flags & WEB_Image) == 0) { - int i, n = dList_length(hc->queue); + int i, n = dList_length(srv->queue); for (i = 0; i < n; i++) { - SocketData_t *curr = dList_nth_data(hc->queue, i); + SocketData_t *curr = dList_nth_data(srv->queue, i); if (a_Web_valid(curr->web) && (curr->web->flags & WEB_Image)) { - dList_insert_pos(hc->queue, sock, i); + dList_insert_pos(srv->queue, sock, i); return; } } } - dList_append(hc->queue, sock); + dList_append(srv->queue, sock); } -static HostConnection_t *Http_host_connection_get(const char *host) +static Server_t *Http_server_get(const char *host, uint_t port, bool_t https) { int i; - HostConnection_t *hc; + Server_t *srv; - for (i = 0; i < dList_length(host_connections); i++) { - hc = (HostConnection_t*) dList_nth_data(host_connections, i); + for (i = 0; i < dList_length(servers); i++) { + srv = (Server_t*) dList_nth_data(servers, i); - if (dStrAsciiCasecmp(host, hc->host) == 0) - return hc; + if (port == srv->port && https == srv->https && + !dStrAsciiCasecmp(host, srv->host)) + return srv; } - hc = dNew0(HostConnection_t, 1); - hc->queue = dList_new(10); - hc->host = dStrdup(host); - dList_append(host_connections, hc); + srv = dNew0(Server_t, 1); + srv->queue = dList_new(10); + srv->host = dStrdup(host); + srv->port = port; + srv->https = https; + dList_append(servers, srv); - return hc; + return srv; } -static void Http_host_connection_remove(HostConnection_t *hc) +static void Http_server_remove(Server_t *srv) { - assert(dList_length(hc->queue) == 0); - dList_free(hc->queue); - dList_remove_fast(host_connections, hc); - dFree(hc->host); - dFree(hc); + assert(dList_length(srv->queue) == 0); + dList_free(srv->queue); + dList_remove_fast(servers, srv); + dFree(srv->host); + dFree(srv); } -static void Http_host_connection_remove_all() +static void Http_servers_remove_all() { - HostConnection_t *hc; + Server_t *srv; SocketData_t *sd; - while (dList_length(host_connections) > 0) { - hc = (HostConnection_t*) dList_nth_data(host_connections, 0); - while ((sd = dList_nth_data(hc->queue, 0))) { - dList_remove(hc->queue, sd); + while (dList_length(servers) > 0) { + srv = (Server_t*) dList_nth_data(servers, 0); + while ((sd = dList_nth_data(srv->queue, 0))) { + dList_remove(srv->queue, sd); dFree(sd); } - Http_host_connection_remove(hc); + Http_server_remove(srv); } - dList_free(host_connections); + dList_free(servers); } static void Http_fd_map_remove_all() @@ -1017,7 +1031,7 @@ static void Http_fd_map_remove_all() */ void a_Http_freeall(void) { - Http_host_connection_remove_all(); + Http_servers_remove_all(); Http_fd_map_remove_all(); a_Klist_free(&ValidSocks); a_Url_free(HTTP_Proxy); -- cgit v1.2.3 From ba68c40f16755a32bf70f49bdf1d39d86adc741e Mon Sep 17 00:00:00 2001 From: corvid Date: Thu, 28 May 2015 18:38:06 +0000 Subject: 'ssl' -> 'tls' where reasonable, given that ssl3 is dead and all I used 'hg rename' and expected (at least hoped) that 'hg diff' would do what I would naturally want, but no. --- AUTHORS | 2 +- configure.ac | 14 +- src/IO/IO.c | 10 +- src/IO/Makefile.am | 4 +- src/IO/http.c | 42 +- src/IO/ssl.c | 1110 ---------------------------------------------------- src/IO/ssl.h | 47 --- src/IO/tls.c | 1110 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/IO/tls.h | 47 +++ src/dillo.cc | 6 +- 10 files changed, 1196 insertions(+), 1196 deletions(-) delete mode 100644 src/IO/ssl.c delete mode 100644 src/IO/ssl.h create mode 100644 src/IO/tls.c create mode 100644 src/IO/tls.h diff --git a/AUTHORS b/AUTHORS index b22eb755..f92ad655 100644 --- a/AUTHORS +++ b/AUTHORS @@ -87,5 +87,5 @@ Non-Dillo code: * src/md5.[ch] contain code by L. Peter Deutsch whose copyright is held by Aladdin Enterprises. * src/tipwin.cc contains code by Greg Ercolano. -* src/IO/ssl.c contains code from wget whose copyright is held by the +* src/IO/tls.c contains code from wget whose copyright is held by the Free Software Foundation. diff --git a/configure.ac b/configure.ac index b574f8d6..aee37a47 100644 --- a/configure.ac +++ b/configure.ac @@ -22,10 +22,10 @@ AC_ARG_ENABLE(gprof, [ --enable-gprof Try to compile and run with pro , enable_gprof=no) AC_ARG_ENABLE(insure, [ --enable-insure Try to compile and run with Insure++], , enable_insure=no) -AC_ARG_ENABLE(ssl, [ --enable-ssl Enable ssl, https (ALPHA CODE)], +AC_ARG_ENABLE(ssl, [ --enable-ssl Enable SSL/HTTPS/TLS (EXPERIMENTAL CODE)], , enable_ssl=no) -AC_ARG_WITH(ca-certs-file, [ --with-ca-certs-file=FILE Specify where to find a bundle of trusted CA certificates for SSL], CA_CERTS_FILE=$withval) -AC_ARG_WITH(ca-certs-dir, [ --with-ca-certs-dir=DIR Specify where to find a directory containing trusted CA certificates for SSL], CA_CERTS_DIR=$withval) +AC_ARG_WITH(ca-certs-file, [ --with-ca-certs-file=FILE Specify where to find a bundle of trusted CA certificates for TLS], CA_CERTS_FILE=$withval) +AC_ARG_WITH(ca-certs-dir, [ --with-ca-certs-dir=DIR Specify where to find a directory containing trusted CA certificates for TLS], CA_CERTS_DIR=$withval) AC_ARG_ENABLE(ipv6, [ --enable-ipv6 Build with support for IPv6], , ) AC_ARG_ENABLE(cookies,[ --disable-cookies Don't compile support for cookies], , enable_cookies=yes) @@ -285,7 +285,7 @@ if test "x$enable_gif" = "xyes"; then fi dnl -------------------------- -dnl Test for support for SSL +dnl Test for support for SSL/TLS dnl -------------------------- dnl if test "x$enable_ssl" = "xyes"; then @@ -299,14 +299,14 @@ if test "x$enable_ssl" = "xyes"; then if test "x$ssl_ok" = "xyes"; then LIBSSL_LIBS="-lcrypto -lssl" - AC_MSG_WARN([*** Enabling ssl support. THIS IS ALPHA CODE!***]) + AC_MSG_WARN([*** Enabling SSL/HTTPS/TLS support. THIS IS EXPERIMENTAL CODE ***]) else - AC_MSG_WARN([*** No libssl found. Disabling ssl support.***]) + AC_MSG_WARN([*** No libssl found. Disabling SSL/HTTPS/TLS support. ***]) fi fi if test "x$ssl_ok" = "xyes"; then - AC_DEFINE([ENABLE_SSL], [1], [Enable SSL support]) + AC_DEFINE([ENABLE_SSL], [1], [Enable SSL/HTTPS/TLS support]) fi dnl -------------------------------------------------------------- diff --git a/src/IO/IO.c b/src/IO/IO.c index e5c5fc79..0cdb9499 100644 --- a/src/IO/IO.c +++ b/src/IO/IO.c @@ -21,7 +21,7 @@ #include "../klist.h" #include "IO.h" #include "iowatch.hh" -#include "ssl.h" +#include "tls.h" /* * Symbolic defines for shutdown() function @@ -163,7 +163,7 @@ static bool_t IO_read(IOData_t *io) ssize_t St; bool_t ret = FALSE; int io_key = io->Key; - void *conn = a_Ssl_connection(io->FD); + void *conn = a_Tls_connection(io->FD); _MSG(" IO_read\n"); @@ -172,7 +172,7 @@ static bool_t IO_read(IOData_t *io) io->Status = 0; while (1) { - St = conn ? a_Ssl_read(conn, Buf, IOBufLen) + St = conn ? a_Tls_read(conn, Buf, IOBufLen) : read(io->FD, Buf, IOBufLen); if (St > 0) { dStr_append_l(io->Buf, Buf, St); @@ -217,13 +217,13 @@ static bool_t IO_write(IOData_t *io) { ssize_t St; bool_t ret = FALSE; - void *conn = a_Ssl_connection(io->FD); + void *conn = a_Tls_connection(io->FD); _MSG(" IO_write\n"); io->Status = 0; while (1) { - St = conn ? a_Ssl_write(conn, io->Buf->str, io->Buf->len) + St = conn ? a_Tls_write(conn, io->Buf->str, io->Buf->len) : write(io->FD, io->Buf->str, io->Buf->len); if (St < 0) { /* Error */ diff --git a/src/IO/Makefile.am b/src/IO/Makefile.am index ff600521..d8fed40a 100644 --- a/src/IO/Makefile.am +++ b/src/IO/Makefile.am @@ -15,8 +15,8 @@ libDiof_a_SOURCES = \ about.c \ Url.h \ http.c \ - ssl.h \ - ssl.c \ + tls.h \ + tls.c \ dpi.c \ IO.c \ iowatch.cc \ diff --git a/src/IO/http.c b/src/IO/http.c index 22b2eaa6..7deab2e4 100644 --- a/src/IO/http.c +++ b/src/IO/http.c @@ -27,7 +27,7 @@ #include /* for inet_ntop */ #include "IO.h" -#include "ssl.h" +#include "tls.h" #include "Url.h" #include "../msg.h" #include "../klist.h" @@ -52,7 +52,7 @@ D_STMT_START { \ static const int HTTP_SOCKET_USE_PROXY = 0x1; static const int HTTP_SOCKET_QUEUED = 0x2; static const int HTTP_SOCKET_TO_BE_FREED = 0x4; -static const int HTTP_SOCKET_SSL = 0x8; +static const int HTTP_SOCKET_TLS = 0x8; /* 'web' is just a reference (no need to deallocate it here). */ typedef struct { @@ -251,16 +251,16 @@ static void Http_connect_queued_sockets(Server_t *srv) sd = dList_nth_data(srv->queue, i); if (!(sd->flags & HTTP_SOCKET_TO_BE_FREED)) { - int connect_ready = SSL_CONNECT_READY; + int connect_ready = TLS_CONNECT_READY; - if (sd->flags & HTTP_SOCKET_SSL) - connect_ready = a_Ssl_connect_ready(sd->url); + if (sd->flags & HTTP_SOCKET_TLS) + connect_ready = a_Tls_connect_ready(sd->url); - if (connect_ready == SSL_CONNECT_NEVER || !a_Web_valid(sd->web)) { + if (connect_ready == TLS_CONNECT_NEVER || !a_Web_valid(sd->web)) { int SKey = VOIDP2INT(sd->Info->LocalKey); Http_socket_free(SKey); - } else if (connect_ready == SSL_CONNECT_READY) { + } else if (connect_ready == TLS_CONNECT_READY) { i--; Http_socket_activate(srv, sd); Http_connect_socket(sd->Info); @@ -295,12 +295,12 @@ static void Http_socket_free(int SKey) } else { if (S->SockFD != -1) Http_fd_map_remove_entry(S->SockFD); - a_Ssl_reset_server_state(S->url); + a_Tls_reset_server_state(S->url); if (S->connected_to) { - a_Ssl_close_by_fd(S->SockFD); + a_Tls_close_by_fd(S->SockFD); Server_t *srv = Http_server_get(S->connected_to, S->connect_port, - (S->flags & HTTP_SOCKET_SSL)); + (S->flags & HTTP_SOCKET_TLS)); srv->active_conns--; Http_connect_queued_sockets(srv); if (srv->active_conns == 0) @@ -484,9 +484,9 @@ static void Http_send_query(SocketData_t *S) /* * Prepare an HTTPS connection. If necessary, tunnel it through a proxy. - * Then perform the SSL handshake. + * Then perform the TLS handshake. */ -static void Http_connect_ssl(ChainLink *info) +static void Http_connect_tls(ChainLink *info) { int SKey = VOIDP2INT(info->LocalKey); SocketData_t *S = a_Klist_get_data(ValidSocks, SKey); @@ -502,7 +502,7 @@ static void Http_connect_ssl(ChainLink *info) dFree(dbuf); dFree(connect_str); } else { - a_Ssl_handshake(S->SockFD, S->url); + a_Tls_handshake(S->SockFD, S->url); } } @@ -573,8 +573,8 @@ static void Http_connect_socket(ChainLink *Info) if (status == -1 && errno != EINPROGRESS) { MSG("Http_connect_socket ERROR: %s\n", dStrerror(errno)); a_Http_connect_done(S->SockFD, FALSE); - } else if (S->flags & HTTP_SOCKET_SSL) { - Http_connect_ssl(Info); + } else if (S->flags & HTTP_SOCKET_TLS) { + Http_connect_tls(Info); } else { a_Http_connect_done(S->SockFD, TRUE); } @@ -677,7 +677,7 @@ static void Http_dns_cb(int Status, Dlist *addr_list, void *data) S->addr_list = addr_list; clean_up = FALSE; srv = Http_server_get(host, S->connect_port, - (S->flags & HTTP_SOCKET_SSL)); + (S->flags & HTTP_SOCKET_TLS)); Http_socket_enqueue(srv, S); Http_connect_queued_sockets(srv); } else { @@ -725,7 +725,7 @@ static int Http_get(ChainLink *Info, void *Data1) S->connect_port = URL_PORT(url); S->url = a_Url_dup(S->web->url); if (!dStrAsciiCasecmp(URL_SCHEME(S->url), "https")) - S->flags |= HTTP_SOCKET_SSL; + S->flags |= HTTP_SOCKET_TLS; /* Let the user know what we'll do */ MSG_BW(S->web, 1, "DNS resolving %s", hostname); @@ -748,11 +748,11 @@ static bool_t Http_socket_reuse_compatible(SocketData_t *old, SocketData_t *new) { /* - * If we are using SSL through a proxy, we need to ensure that old and new + * If we are using TLS through a proxy, we need to ensure that old and new * are going through to the same host:port. */ if (a_Web_valid(new->web) && - ((old->flags & HTTP_SOCKET_SSL) == 0 || + ((old->flags & HTTP_SOCKET_TLS) == 0 || (old->flags & HTTP_SOCKET_USE_PROXY) == 0 || ((URL_PORT(old->url) == URL_PORT(new->url)) && !dStrAsciiCasecmp(URL_HOST(old->url), URL_HOST(new->url))))) @@ -771,7 +771,7 @@ static void Http_socket_reuse(int SKey) if (old_sd) { Server_t *srv = Http_server_get(old_sd->connected_to, old_sd->connect_port, - (old_sd->flags & HTTP_SOCKET_SSL)); + (old_sd->flags & HTTP_SOCKET_TLS)); int i, n = dList_length(srv->queue); for (i = 0; i < n; i++) { @@ -874,7 +874,7 @@ void a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info, sd->https_proxy_reply->str); dStr_free(sd->https_proxy_reply, 1); sd->https_proxy_reply = NULL; - a_Ssl_handshake(sd->SockFD, sd->url); + a_Tls_handshake(sd->SockFD, sd->url); } else { MSG_BW(sd->web, 1, "Can't connect through proxy to %s", URL_HOST(sd->url)); diff --git a/src/IO/ssl.c b/src/IO/ssl.c deleted file mode 100644 index 856d94b5..00000000 --- a/src/IO/ssl.c +++ /dev/null @@ -1,1110 +0,0 @@ -/* - * File: ssl.c - * - * Copyright 2004 Garrett Kajmowicz - * (for some bits derived from the https dpi, e.g., certificate handling) - * Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, - * 2009, 2010, 2011, 2012 Free Software Foundation, Inc. - * (for the certificate hostname checking from wget) - * Copyright (C) 2011 Benjamin Johnson - * (for the https code offered from dplus browser that formed the basis...) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * As a special exception, permission is granted to link Dillo with the OpenSSL - * or LibreSSL library, and distribute the linked executables without - * including the source code for OpenSSL or LibreSSL in the source - * distribution. You must obey the GNU General Public License, version 3, in - * all respects for all of the code used other than OpenSSL or LibreSSL. - */ - -/* https://www.ssllabs.com/ssltest/viewMyClient.html */ - -/* - * Using TLS in Applications: http://datatracker.ietf.org/wg/uta/documents/ - * TLS: http://datatracker.ietf.org/wg/tls/documents/ - */ - -#include "config.h" -#include "../msg.h" - -#ifndef ENABLE_SSL - -void a_Ssl_init() -{ - MSG("SSL: Disabled at compilation time.\n"); -} - -#else - -#include - -#include -#include - -#include /* tolower for wget stuff */ -#include -#include -#include "../../dlib/dlib.h" -#include "../dialog.hh" -#include "../klist.h" -#include "iowatch.hh" -#include "ssl.h" -#include "Url.h" - -#include -#include -#include -#include /* for hostname checking */ - -#define CERT_STATUS_NONE 0 -#define CERT_STATUS_RECEIVING 1 -#define CERT_STATUS_GOOD 2 -#define CERT_STATUS_BAD 3 -#define CERT_STATUS_USER_ACCEPTED 4 - -typedef struct { - char *hostname; - int port; - int cert_status; -} Server_t; - -typedef struct { - int fd; - int connkey; -} FdMapEntry_t; - -/* - * Data type for SSL connection information - */ -typedef struct { - int fd; - DilloUrl *url; - SSL *ssl; - bool_t connecting; -} Conn_t; - -/* List of active SSL connections */ -static Klist_t *conn_list = NULL; - -/* - * If ssl_context is still NULL, this corresponds to SSL being disabled. - */ -static SSL_CTX *ssl_context; -static Dlist *servers; -static Dlist *fd_map; - -static void Ssl_connect_cb(int fd, void *vssl); - -/* - * Compare by FD. - */ -static int Ssl_fd_map_cmp(const void *v1, const void *v2) -{ - int fd = VOIDP2INT(v2); - const FdMapEntry_t *e = v1; - - return (fd != e->fd); -} - -static void Ssl_fd_map_add_entry(int fd, int connkey) -{ - FdMapEntry_t *e = dNew0(FdMapEntry_t, 1); - e->fd = fd; - e->connkey = connkey; - - if (dList_find_custom(fd_map, INT2VOIDP(e->fd), Ssl_fd_map_cmp)) { - MSG_ERR("SSL FD ENTRY ALREADY FOUND FOR %d\n", e->fd); - assert(0); - } - - dList_append(fd_map, e); -//MSG("ADD ENTRY %d %s\n", e->fd, URL_STR(sd->url)); -} - -/* - * Remove and free entry from fd_map. - */ -static void Ssl_fd_map_remove_entry(int fd) -{ - void *data = dList_find_custom(fd_map, INT2VOIDP(fd), Ssl_fd_map_cmp); - -//MSG("REMOVE ENTRY %d\n", fd); - if (data) { - dList_remove_fast(fd_map, data); - dFree(data); - } else { - MSG("SSL FD ENTRY NOT FOUND FOR %d\n", fd); - } -} - -/* - * Return SSL connection information for a given file - * descriptor, or NULL if no SSL connection was found. - */ -void *a_Ssl_connection(int fd) -{ - Conn_t *conn; - - if (fd_map) { - FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd), - Ssl_fd_map_cmp); - - if (fme && (conn = a_Klist_get_data(conn_list, fme->connkey))) - return conn; - } - return NULL; -} - -/* - * Add a new SSL connection information node. - */ -static int Ssl_conn_new(int fd, const DilloUrl *url, SSL *ssl) -{ - int key; - - Conn_t *conn = dNew0(Conn_t, 1); - conn->fd = fd; - conn->url = a_Url_dup(url); - conn->ssl = ssl; - conn->connecting = TRUE; - - key = a_Klist_insert(&conn_list, conn); - - Ssl_fd_map_add_entry(fd, key); - - return key; -} - -/* - * Let's monitor for ssl alerts. - */ -static void Ssl_info_cb(const SSL *ssl, int where, int ret) -{ - if (where & SSL_CB_ALERT) { - MSG("SSL ALERT on %s: %s\n", (where & SSL_CB_READ) ? "read" : "write", - SSL_alert_desc_string_long(ret)); - } -} - -/* - * Load trusted certificates. - * This is like using SSL_CTX_load_verify_locations() but permitting more - * than one bundle and more than one directory. Due to the notoriously - * abysmal openssl documentation, this was worked out from reading discussion - * on the web and then reading openssl source to see what it normally does. - */ -static void Ssl_load_certificates() -{ - /* curl-7.37.1 says that the following bundle locations are used on "Debian - * systems", "Redhat and Mandriva", "old(er) Redhat", "FreeBSD", and - * "OpenBSD", respectively -- and that the /etc/ssl/certs/ path is needed on - * "SUSE". No doubt it's all changed some over time, but this gives us - * something to work with. - */ - uint_t u; - char *userpath; - static const char *ca_files[] = { - "/etc/ssl/certs/ca-certificates.crt", - "/etc/pki/tls/certs/ca-bundle.crt", - "/usr/share/ssl/certs/ca-bundle.crt", - "/usr/local/share/certs/ca-root.crt", - "/etc/ssl/cert.pem", - CA_CERTS_FILE - }; - - static const char *ca_paths[] = { - "/etc/ssl/certs/", - CA_CERTS_DIR - }; - - X509_STORE *store = SSL_CTX_get_cert_store(ssl_context); - X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); - - for (u = 0; u < sizeof(ca_files) / sizeof(ca_files[0]); u++) { - if (*ca_files[u]) - X509_LOOKUP_load_file(lookup, ca_files[u], X509_FILETYPE_PEM); - } - - lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir()); - for (u = 0; u < sizeof(ca_paths)/sizeof(ca_paths[0]); u++) { - if (*ca_paths[u]) - X509_LOOKUP_add_dir(lookup, ca_paths[u], X509_FILETYPE_PEM); - } - - userpath = dStrconcat(dGethomedir(), "/.dillo/certs/", NULL); - X509_LOOKUP_add_dir(lookup, userpath, X509_FILETYPE_PEM); - dFree(userpath); - - /* Clear out errors in the queue (file not found, etc.) */ - while(ERR_get_error()) - ; -} - -/* - * Initialize the OpenSSL library. - */ -void a_Ssl_init(void) -{ - SSL_library_init(); - SSL_load_error_strings(); - if (RAND_status() != 1) { - /* The standard solution is to provide it with more entropy, but this - * involves knowing very well that you are doing exactly the right thing. - */ - MSG_ERR("Disabling HTTPS: Insufficient entropy for openssl.\n"); - return; - } - - /* Create SSL context */ - ssl_context = SSL_CTX_new(SSLv23_client_method()); - if (ssl_context == NULL) { - MSG_ERR("Disabling HTTPS: Error creating SSL context.\n"); - return; - } - - SSL_CTX_set_info_callback(ssl_context, Ssl_info_cb); - - /* Don't want: eNULL, which has no encryption; aNULL, which has no - * authentication; LOW, which as of 2014 use 64 or 56-bit encryption; - * EXPORT40, which uses 40-bit encryption; RC4, for which methods were - * found in 2013 to defeat it somewhat too easily. - */ - SSL_CTX_set_cipher_list(ssl_context, - "ALL:!aNULL:!eNULL:!LOW:!EXPORT40:!RC4"); - - /* SSL2 has been known to be insecure forever, disabling SSL3 is in response - * to POODLE, and disabling compression is in response to CRIME. - */ - SSL_CTX_set_options(ssl_context, - SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_COMPRESSION); - - /* This lets us deal with self-signed certificates */ - SSL_CTX_set_verify(ssl_context, SSL_VERIFY_NONE, NULL); - - Ssl_load_certificates(); - - fd_map = dList_new(20); - servers = dList_new(8); -} - -/* - * Save certificate with a hashed filename. - * Return: 0 on success, 1 on failure. - */ -static int Ssl_save_certificate_home(X509 * cert) -{ - char buf[4096]; - - FILE * fp = NULL; - uint_t i = 0; - int ret = 1; - - /* Attempt to create .dillo/certs blindly - check later */ - snprintf(buf, 4096, "%s/.dillo/", dGethomedir()); - mkdir(buf, 01777); - snprintf(buf, 4096, "%s/.dillo/certs/", dGethomedir()); - mkdir(buf, 01777); - - do { - snprintf(buf, 4096, "%s/.dillo/certs/%lx.%u", - dGethomedir(), X509_subject_name_hash(cert), i); - - fp=fopen(buf, "r"); - if (fp == NULL){ - /* File name doesn't exist so we can use it safely */ - fp=fopen(buf, "w"); - if (fp == NULL){ - MSG("Unable to open cert save file in home dir\n"); - break; - } else { - PEM_write_X509(fp, cert); - fclose(fp); - MSG("Wrote certificate\n"); - ret = 0; - break; - } - } else { - fclose(fp); - } - i++; - /* Don't loop too many times - just give up */ - } while (i < 1024); - - return ret; -} - -/* - * Test whether a URL corresponds to a server. - */ -static int Ssl_servers_cmp(const void *v1, const void *v2) -{ - Server_t *s = (Server_t *)v1; - const DilloUrl *url = (const DilloUrl *)v2; - const char *host = URL_HOST(url); - int port = URL_PORT(url); - - return (dStrAsciiCasecmp(s->hostname, host) || (port != s->port)); -} - -/* - * The purpose here is to permit a single initial connection to a server. - * Once we have the certificate, know whether we like it -- and whether the - * user accepts it -- HTTP can run through queued sockets as normal. - * - * Return: 1 means yes, 0 means not yet, -1 means never. - * TODO: Something clearer or different. - */ -int a_Ssl_connect_ready(const DilloUrl *url) -{ - Server_t *s; - int i, len; - const char *host = URL_HOST(url); - const int port = URL_PORT(url); - int ret = SSL_CONNECT_READY; - - dReturn_val_if_fail(ssl_context, SSL_CONNECT_NEVER); - - len = dList_length(servers); - - for (i = 0; i < len; i++) { - s = dList_nth_data(servers, i); - - if (!dStrAsciiCasecmp(s->hostname, host) && (port == s->port)) { - if (s->cert_status == CERT_STATUS_RECEIVING) - ret = SSL_CONNECT_NOT_YET; - else if (s->cert_status == CERT_STATUS_BAD) - ret = SSL_CONNECT_NEVER; - - if (s->cert_status == CERT_STATUS_NONE) - s->cert_status = CERT_STATUS_RECEIVING; - return ret; - } - } - s = dNew(Server_t, 1); - - s->port = port; - s->hostname = dStrdup(host); - s->cert_status = CERT_STATUS_RECEIVING; - dList_append(servers, s); - return ret; -} - -/* - * Did we find problems with the certificate, and did the user proceed to - * reject the connection? - */ -static int Ssl_user_said_no(const DilloUrl *url) -{ - Server_t *s = dList_find_custom(servers, url, Ssl_servers_cmp); - - if (!s) - return FALSE; - - return s->cert_status == CERT_STATUS_BAD; -} - -/* - * Did we find problems with the certificate, and did the user proceed to - * accept the connection anyway? - */ -static int Ssl_user_said_yes(const DilloUrl *url) -{ - Server_t *s = dList_find_custom(servers, url, Ssl_servers_cmp); - - if (!s) - return FALSE; - - return s->cert_status == CERT_STATUS_USER_ACCEPTED; -} - -/******************** BEGINNING OF STUFF DERIVED FROM wget-1.16.3 */ - -#define ASTERISK_EXCLUDES_DOT /* mandated by rfc2818 */ - -/* Return true is STRING (case-insensitively) matches PATTERN, false - otherwise. The recognized wildcard character is "*", which matches - any character in STRING except ".". Any number of the "*" wildcard - may be present in the pattern. - - This is used to match of hosts as indicated in rfc2818: "Names may - contain the wildcard character * which is considered to match any - single domain name component or component fragment. E.g., *.a.com - matches foo.a.com but not bar.foo.a.com. f*.com matches foo.com but - not bar.com [or foo.bar.com]." - - If the pattern contain no wildcards, pattern_match(a, b) is - equivalent to !strcasecmp(a, b). */ - -static bool_t pattern_match (const char *pattern, const char *string) -{ - - const char *p = pattern, *n = string; - char c; - for (; (c = tolower (*p++)) != '\0'; n++) - if (c == '*') - { - for (c = tolower (*p); c == '*'; c = tolower (*++p)) - ; - for (; *n != '\0'; n++) - if (tolower (*n) == c && pattern_match (p, n)) - return TRUE; -#ifdef ASTERISK_EXCLUDES_DOT - else if (*n == '.') - return FALSE; -#endif - return c == '\0'; - } - else - { - if (c != tolower (*n)) - return FALSE; - } - return *n == '\0'; -} - -static bool_t Ssl_check_cert_hostname(X509 *cert, const DilloUrl *url, - int *choice) -{ - dReturn_val_if_fail(cert && url, -1); - - char *msg; - const char *host = URL_HOST(url); - GENERAL_NAMES *subjectAltNames; - bool_t success = TRUE, alt_name_checked = FALSE;; - char common_name[256]; - - /* Check that HOST matches the common name in the certificate. - #### The following remains to be done: - - - When matching against common names, it should loop over all - common names and choose the most specific one, i.e. the last - one, not the first one, which the current code picks. - - - Ensure that ASN1 strings from the certificate are encoded as - UTF-8 which can be meaningfully compared to HOST. */ - - subjectAltNames = X509_get_ext_d2i (cert, NID_subject_alt_name, NULL, NULL); - - if (subjectAltNames) - { - /* Test subject alternative names */ - - /* Do we want to check for dNSNAmes or ipAddresses (see RFC 2818)? - * Signal it by host_in_octet_string. */ - ASN1_OCTET_STRING *host_in_octet_string = a2i_IPADDRESS (host); - - int numaltnames = sk_GENERAL_NAME_num (subjectAltNames); - int i; - for (i=0; i < numaltnames; i++) - { - const GENERAL_NAME *name = - sk_GENERAL_NAME_value (subjectAltNames, i); - if (name) - { - if (host_in_octet_string) - { - if (name->type == GEN_IPADD) - { - /* Check for ipAddress */ - /* TODO: Should we convert between IPv4-mapped IPv6 - * addresses and IPv4 addresses? */ - alt_name_checked = TRUE; - if (!ASN1_STRING_cmp (host_in_octet_string, - name->d.iPAddress)) - break; - } - } - else if (name->type == GEN_DNS) - { - /* dNSName should be IA5String (i.e. ASCII), however who - * does trust CA? Convert it into UTF-8 for sure. */ - unsigned char *name_in_utf8 = NULL; - - /* Check for dNSName */ - alt_name_checked = TRUE; - - if (0 <= ASN1_STRING_to_UTF8 (&name_in_utf8, name->d.dNSName)) - { - /* Compare and check for NULL attack in ASN1_STRING */ - if (pattern_match ((char *)name_in_utf8, host) && - (strlen ((char *)name_in_utf8) == - (size_t)ASN1_STRING_length (name->d.dNSName))) - { - OPENSSL_free (name_in_utf8); - break; - } - OPENSSL_free (name_in_utf8); - } - } - } - } - sk_GENERAL_NAME_pop_free(subjectAltNames, GENERAL_NAME_free); - if (host_in_octet_string) - ASN1_OCTET_STRING_free(host_in_octet_string); - - if (alt_name_checked == TRUE && i >= numaltnames) - { - success = FALSE; - msg = dStrconcat("No certificate subject alternative name matches" - " requested host name \n", host, NULL); - *choice = a_Dialog_choice("Dillo SSL security warning", - msg, "Continue", "Cancel", NULL); - dFree(msg); - - switch (*choice){ - case 1: - success = TRUE; - break; - case 2: - break; - default: - break; - } - } - } - - if (alt_name_checked == FALSE) - { - /* Test commomName */ - X509_NAME *xname = X509_get_subject_name(cert); - common_name[0] = '\0'; - X509_NAME_get_text_by_NID (xname, NID_commonName, common_name, - sizeof (common_name)); - - if (!pattern_match (common_name, host)) - { - success = FALSE; - msg = dStrconcat("Certificate common name ", common_name, - " doesn't match requested host name ", host, NULL); - *choice = a_Dialog_choice("Dillo SSL security warning", - msg, "Continue", "Cancel", NULL); - dFree(msg); - - switch (*choice){ - case 1: - success = TRUE; - break; - case 2: - break; - default: - break; - } - } - else - { - /* We now determine the length of the ASN1 string. If it - * differs from common_name's length, then there is a \0 - * before the string terminates. This can be an instance of a - * null-prefix attack. - * - * https://www.blackhat.com/html/bh-usa-09/bh-usa-09-archives.html#Marlinspike - * */ - - int i = -1, j; - X509_NAME_ENTRY *xentry; - ASN1_STRING *sdata; - - if (xname) { - for (;;) - { - j = X509_NAME_get_index_by_NID (xname, NID_commonName, i); - if (j == -1) break; - i = j; - } - } - - xentry = X509_NAME_get_entry(xname,i); - sdata = X509_NAME_ENTRY_get_data(xentry); - if (strlen (common_name) != (size_t)ASN1_STRING_length (sdata)) - { - success = FALSE; - msg = dStrconcat("Certificate common name is invalid (contains a NUL " - "character). This may be an indication that the " - "host is not who it claims to be -- that is, not " - "the real ", host, NULL); - *choice = a_Dialog_choice("Dillo SSL security warning", - msg, "Continue", "Cancel", NULL); - dFree(msg); - - switch (*choice){ - case 1: - success = TRUE; - break; - case 2: - break; - default: - break; - } - } - } - } - return success; -} - -/******************** END OF STUFF DERIVED FROM wget-1.16.3 */ - -/* - * Examine the certificate, and, if problems are detected, ask the user what - * to do. - * Return: -1 if connection should be canceled, or 0 if it should continue. - */ -static int Ssl_examine_certificate(SSL *ssl, const DilloUrl *url) -{ - X509 *remote_cert; - long st; - char buf[4096], *cn, *msg; - int choice = -1, ret = -1; - char *title = dStrconcat("Dillo SSL security warning: ",URL_HOST(url),NULL); - Server_t *srv = dList_find_custom(servers, url, Ssl_servers_cmp); - - remote_cert = SSL_get_peer_certificate(ssl); - if (remote_cert == NULL){ - /* Inform user that remote system cannot be trusted */ - choice = a_Dialog_choice(title, - "The remote system is not presenting a certificate. " - "This site cannot be trusted. Sending data is not safe.", - "Continue", "Cancel", NULL); - - /* Abort on anything but "Continue" */ - if (choice == 1){ - ret = 0; - } - - } else if (Ssl_check_cert_hostname(remote_cert, url, &choice)) { - /* Figure out if (and why) the remote system can't be trusted */ - st = SSL_get_verify_result(ssl); - switch (st) { - case X509_V_OK: - ret = 0; - break; - case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: - /* Either self signed and untrusted */ - /* Extract CN from certificate name information */ - if ((cn = strstr(remote_cert->name, "/CN=")) == NULL) { - strcpy(buf, "(no CN given)"); - } else { - char *cn_end; - - cn += 4; - - if ((cn_end = strstr(cn, "/")) == NULL ) - cn_end = cn + strlen(cn); - - strncpy(buf, cn, (size_t) (cn_end - cn)); - buf[cn_end - cn] = '\0'; - } - msg = dStrconcat("The remote certificate is self-signed and " - "untrusted. For address: ", buf, NULL); - choice = a_Dialog_choice(title, - msg, "Continue", "Cancel", "Save Certificate", NULL); - dFree(msg); - - switch (choice){ - case 1: - ret = 0; - break; - case 2: - break; - case 3: - /* Save certificate to a file here and recheck the chain */ - /* Potential security problems because we are writing - * to the filesystem */ - Ssl_save_certificate_home(remote_cert); - ret = 1; - break; - default: - break; - } - break; - case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: - case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: - choice = a_Dialog_choice(title, - "The issuer for the remote certificate cannot be found. " - "The authenticity of the remote certificate cannot be trusted.", - "Continue", "Cancel", NULL); - - if (choice == 1) { - ret = 0; - } - break; - - case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: - case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: - case X509_V_ERR_CERT_SIGNATURE_FAILURE: - case X509_V_ERR_CRL_SIGNATURE_FAILURE: - choice = a_Dialog_choice(title, - "The remote certificate signature could not be read " - "or is invalid and should not be trusted", - "Continue", "Cancel", NULL); - - if (choice == 1) { - ret = 0; - } - break; - case X509_V_ERR_CERT_NOT_YET_VALID: - case X509_V_ERR_CRL_NOT_YET_VALID: - choice = a_Dialog_choice(title, - "Part of the remote certificate is not yet valid. " - "Certificates usually have a range of dates over which " - "they are to be considered valid, and the certificate " - "presented has a starting validity after today's date " - "You should be cautious about using this site", - "Continue", "Cancel", NULL); - - if (choice == 1) { - ret = 0; - } - break; - case X509_V_ERR_CERT_HAS_EXPIRED: - case X509_V_ERR_CRL_HAS_EXPIRED: - choice = a_Dialog_choice(title, - "The remote certificate has expired. The certificate " - "wasn't designed to last this long. You should avoid " - "this site.", - "Continue", "Cancel", NULL); - if (choice == 1) { - ret = 0; - } - break; - case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: - case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: - case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: - case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: - choice = a_Dialog_choice(title, - "There was an error in the certificate presented. " - "Some of the certificate data was improperly formatted " - "making it impossible to determine if the certificate " - "is valid. You should not trust this certificate.", - "Continue", "Cancel", NULL); - if (choice == 1) { - ret = 0; - } - break; - case X509_V_ERR_INVALID_CA: - case X509_V_ERR_INVALID_PURPOSE: - case X509_V_ERR_CERT_UNTRUSTED: - case X509_V_ERR_CERT_REJECTED: - case X509_V_ERR_KEYUSAGE_NO_CERTSIGN: - choice = a_Dialog_choice(title, - "One of the certificates in the chain is being used " - "incorrectly (possibly due to configuration problems " - "with the remote system. The connection should not " - "be trusted", - "Continue", "Cancel", NULL); - if (choice == 1) { - ret = 0; - } - break; - case X509_V_ERR_SUBJECT_ISSUER_MISMATCH: - case X509_V_ERR_AKID_SKID_MISMATCH: - case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH: - choice = a_Dialog_choice(title, - "Some of the information presented by the remote system " - "does not match other information presented. " - "This may be an attempt to eavesdrop on communications", - "Continue", "Cancel", NULL); - if (choice == 1) { - ret = 0; - } - break; - case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: - choice = a_Dialog_choice(title, - "Self signed certificate in certificate chain. The certificate " - "chain could be built up using the untrusted certificates but the " - "root could not be found locally.", - "Continue", "Cancel", NULL); - if (choice == 1) { - ret = 0; - } - break; - case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: - choice = a_Dialog_choice(title, - "Unable to get local issuer certificate. The issuer certificate " - "of an untrusted certificate cannot be found.", - "Continue", "Cancel", NULL); - if (choice == 1) { - ret = 0; - } - break; - default: /* Need to add more options later */ - snprintf(buf, 80, - "The remote certificate cannot be verified (code %ld)", st); - choice = a_Dialog_choice(title, - buf, "Continue", "Cancel", NULL); - /* abort on anything but "Continue" */ - if (choice == 1){ - ret = 0; - } - } - X509_free(remote_cert); - remote_cert = 0; - } - dFree(title); - - if (choice == 2) - srv->cert_status = CERT_STATUS_BAD; - else if (choice == -1) - srv->cert_status = CERT_STATUS_GOOD; - else - srv->cert_status = CERT_STATUS_USER_ACCEPTED; - - return ret; -} - -/* - * If the connection was closed before we got the certificate, we need to - * reset state so that we'll try again. - */ -void a_Ssl_reset_server_state(const DilloUrl *url) -{ - if (servers) { - Server_t *s = dList_find_custom(servers, url, Ssl_servers_cmp); - - if (s && s->cert_status == CERT_STATUS_RECEIVING) - s->cert_status = CERT_STATUS_NONE; - } -} - -/* - * Close an open SSL connection. - */ -static void Ssl_close_by_key(int connkey) -{ - Conn_t *c; - - if ((c = a_Klist_get_data(conn_list, connkey))) { - a_Ssl_reset_server_state(c->url); - if (c->connecting) { - a_IOwatch_remove_fd(c->fd, -1); - dClose(c->fd); - } - SSL_shutdown(c->ssl); - SSL_free(c->ssl); - - a_Url_free(c->url); - Ssl_fd_map_remove_entry(c->fd); - a_Klist_remove(conn_list, connkey); - dFree(c); - } -} - -/* - * Connect, set a callback if it's still not completed. If completed, check - * the certificate and report back to http. - */ -static void Ssl_connect(int fd, int connkey) -{ - int ret; - bool_t ongoing = FALSE, failed = TRUE; - Conn_t *conn; - - if (!(conn = a_Klist_get_data(conn_list, connkey))) { - MSG("Ssl_connect: conn for fd %d not valid\n", fd); - return; - } - - assert(!ERR_get_error()); - - ret = SSL_connect(conn->ssl); - - if (ret <= 0) { - int err1_ret = SSL_get_error(conn->ssl, ret); - if (err1_ret == SSL_ERROR_WANT_READ || - err1_ret == SSL_ERROR_WANT_WRITE) { - int want = err1_ret == SSL_ERROR_WANT_READ ? DIO_READ : DIO_WRITE; - - _MSG("iowatching fd %d for ssl -- want %s\n", fd, - err1_ret == SSL_ERROR_WANT_READ ? "read" : "write"); - a_IOwatch_remove_fd(fd, -1); - a_IOwatch_add_fd(fd, want, Ssl_connect_cb, INT2VOIDP(connkey)); - ongoing = TRUE; - failed = FALSE; - } else if (err1_ret == SSL_ERROR_SYSCALL || err1_ret == SSL_ERROR_SSL) { - unsigned long err2_ret = ERR_get_error(); - - if (err2_ret) { - do { - MSG("SSL_connect() failed: %s\n", - ERR_error_string(err2_ret, NULL)); - } while ((err2_ret = ERR_get_error())); - } else { - /* nothing in the error queue */ - if (ret == 0) { - MSG("SSL connect error: \"an EOF was observed that violates " - "the protocol\"\n"); - /* - * I presume we took too long on our side and the server grew - * impatient. - */ - } else if (ret == -1) { - MSG("SSL connect error: %s\n", dStrerror(errno)); - - /* If the following can happen, I'll add code to handle it, but - * I don't want to add code blindly if it isn't getting used - */ - assert(errno != EAGAIN && errno != EINTR); - } else { - MSG_ERR("According to the man page for SSL_get_error(), this " - "was not a possibility (ret %d).\n", ret); - } - } - } else { - MSG("SSL_get_error() returned %d on a connect.\n", err1_ret); - } - } else { - if (Ssl_user_said_yes(conn->url) || - (Ssl_examine_certificate(conn->ssl, conn->url) != -1)) - failed = FALSE; - } - - /* - * If there were problems with the certificate, the connection may have - * been closed by the server if the user responded too slowly to a popup. - */ - - if (!ongoing) { - if (a_Klist_get_data(conn_list, connkey)) { - conn->connecting = FALSE; - if (failed) { - Ssl_close_by_key(connkey); - } - a_IOwatch_remove_fd(fd, DIO_READ|DIO_WRITE); - a_Http_connect_done(fd, failed ? FALSE : TRUE); - } else { - MSG("Connection disappeared. Too long with a popup popped up?\n"); - } - } -} - -static void Ssl_connect_cb(int fd, void *vconnkey) -{ - Ssl_connect(fd, VOIDP2INT(vconnkey)); -} - -/* - * Perform the SSL handshake on an open socket. - */ -void a_Ssl_handshake(int fd, const DilloUrl *url) -{ - SSL *ssl; - bool_t success = TRUE; - int connkey = -1; - - if (!ssl_context) - success = FALSE; - - if (success && Ssl_user_said_no(url)) { - success = FALSE; - } - - assert(!ERR_get_error()); - - if (success && !(ssl = SSL_new(ssl_context))) { - unsigned long err_ret = ERR_get_error(); - do { - MSG("SSL_new() failed: %s\n", ERR_error_string(err_ret, NULL)); - } while ((err_ret = ERR_get_error())); - success = FALSE; - } - - /* assign SSL connection to this file descriptor */ - if (success && !SSL_set_fd(ssl, fd)) { - unsigned long err_ret = ERR_get_error(); - do { - MSG("SSL_set_fd() failed: %s\n", ERR_error_string(err_ret, NULL)); - } while ((err_ret = ERR_get_error())); - success = FALSE; - } - - if (success) - connkey = Ssl_conn_new(fd, url, ssl); - -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - /* Server Name Indication. From the openssl changelog, it looks like this - * came along in 2010. - */ - if (success && !a_Url_host_is_ip(URL_HOST(url))) - SSL_set_tlsext_host_name(ssl, URL_HOST(url)); -#endif - - if (!success) { - a_Ssl_reset_server_state(url); - a_Http_connect_done(fd, success); - } else { - Ssl_connect(fd, connkey); - } -} - -/* - * Read data from an open SSL connection. - */ -int a_Ssl_read(void *conn, void *buf, size_t len) -{ - Conn_t *c = (Conn_t*)conn; - return SSL_read(c->ssl, buf, len); -} - -/* - * Write data to an open SSL connection. - */ -int a_Ssl_write(void *conn, void *buf, size_t len) -{ - Conn_t *c = (Conn_t*)conn; - return SSL_write(c->ssl, buf, len); -} - -void a_Ssl_close_by_fd(int fd) -{ - FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd), - Ssl_fd_map_cmp); - - if (fme) { - Ssl_close_by_key(fme->connkey); - } -} - -static void Ssl_servers_freeall() -{ - if (servers) { - Server_t *s; - int i, n = dList_length(servers); - - for (i = 0; i < n; i++) { - s = (Server_t *) dList_nth_data(servers, i); - dFree(s->hostname); - dFree(s); - } - dList_free(servers); - } -} - -static void Ssl_fd_map_remove_all() -{ - if (fd_map) { - FdMapEntry_t *fme; - int i, n = dList_length(fd_map); - - for (i = 0; i < n; i++) { - fme = (FdMapEntry_t *) dList_nth_data(fd_map, i); - dFree(fme); - } - dList_free(fd_map); - } -} - -/* - * Clean up the OpenSSL library - */ -void a_Ssl_freeall(void) -{ - if (ssl_context) - SSL_CTX_free(ssl_context); - Ssl_fd_map_remove_all(); - Ssl_servers_freeall(); -} - -#endif /* ENABLE_SSL */ diff --git a/src/IO/ssl.h b/src/IO/ssl.h deleted file mode 100644 index f55479b2..00000000 --- a/src/IO/ssl.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef __SSL_H__ -#define __SSL_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "../url.h" - -#define SSL_CONNECT_NEVER -1 -#define SSL_CONNECT_NOT_YET 0 -#define SSL_CONNECT_READY 1 - -void a_Ssl_init(); - - -#ifdef ENABLE_SSL -int a_Ssl_connect_ready(const DilloUrl *url); -void a_Ssl_reset_server_state(const DilloUrl *url); - -/* Use to initiate a SSL connection. */ -void a_Ssl_handshake(int fd, const DilloUrl *url); - -void *a_Ssl_connection(int fd); - -void a_Ssl_freeall(); - -void a_Ssl_close_by_fd(int fd); -int a_Ssl_read(void *conn, void *buf, size_t len); -int a_Ssl_write(void *conn, void *buf, size_t len); -#else - -#define a_Ssl_connect_ready(url) SSL_CONNECT_NEVER -#define a_Ssl_reset_server_state(url) ; -#define a_Ssl_handshake(fd, url) ; -#define a_Ssl_connection(fd) NULL -#define a_Ssl_freeall() ; -#define a_Ssl_close_by_fd(fd) ; -#define a_Ssl_read(conn, buf, len) 0 -#define a_Ssl_write(conn, buf, len) 0 -#endif -#ifdef __cplusplus -} -#endif - -#endif /* __SSL_H__ */ - diff --git a/src/IO/tls.c b/src/IO/tls.c new file mode 100644 index 00000000..39252635 --- /dev/null +++ b/src/IO/tls.c @@ -0,0 +1,1110 @@ +/* + * File: tls.c + * + * Copyright 2004 Garrett Kajmowicz + * (for some bits derived from the https dpi, e.g., certificate handling) + * Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, + * 2009, 2010, 2011, 2012 Free Software Foundation, Inc. + * (for the certificate hostname checking from wget) + * Copyright (C) 2011 Benjamin Johnson + * (for the https code offered from dplus browser that formed the basis...) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * As a special exception, permission is granted to link Dillo with the OpenSSL + * or LibreSSL library, and distribute the linked executables without + * including the source code for OpenSSL or LibreSSL in the source + * distribution. You must obey the GNU General Public License, version 3, in + * all respects for all of the code used other than OpenSSL or LibreSSL. + */ + +/* https://www.ssllabs.com/ssltest/viewMyClient.html */ + +/* + * Using TLS in Applications: http://datatracker.ietf.org/wg/uta/documents/ + * TLS: http://datatracker.ietf.org/wg/tls/documents/ + */ + +#include "config.h" +#include "../msg.h" + +#ifndef ENABLE_SSL + +void a_Tls_init() +{ + MSG("TLS: Disabled at compilation time.\n"); +} + +#else + +#include + +#include +#include + +#include /* tolower for wget stuff */ +#include +#include +#include "../../dlib/dlib.h" +#include "../dialog.hh" +#include "../klist.h" +#include "iowatch.hh" +#include "tls.h" +#include "Url.h" + +#include +#include +#include +#include /* for hostname checking */ + +#define CERT_STATUS_NONE 0 +#define CERT_STATUS_RECEIVING 1 +#define CERT_STATUS_GOOD 2 +#define CERT_STATUS_BAD 3 +#define CERT_STATUS_USER_ACCEPTED 4 + +typedef struct { + char *hostname; + int port; + int cert_status; +} Server_t; + +typedef struct { + int fd; + int connkey; +} FdMapEntry_t; + +/* + * Data type for TLS connection information + */ +typedef struct { + int fd; + DilloUrl *url; + SSL *ssl; + bool_t connecting; +} Conn_t; + +/* List of active TLS connections */ +static Klist_t *conn_list = NULL; + +/* + * If ssl_context is still NULL, this corresponds to TLS being disabled. + */ +static SSL_CTX *ssl_context; +static Dlist *servers; +static Dlist *fd_map; + +static void Tls_connect_cb(int fd, void *vconnkey); + +/* + * Compare by FD. + */ +static int Tls_fd_map_cmp(const void *v1, const void *v2) +{ + int fd = VOIDP2INT(v2); + const FdMapEntry_t *e = v1; + + return (fd != e->fd); +} + +static void Tls_fd_map_add_entry(int fd, int connkey) +{ + FdMapEntry_t *e = dNew0(FdMapEntry_t, 1); + e->fd = fd; + e->connkey = connkey; + + if (dList_find_custom(fd_map, INT2VOIDP(e->fd), Tls_fd_map_cmp)) { + MSG_ERR("TLS FD ENTRY ALREADY FOUND FOR %d\n", e->fd); + assert(0); + } + + dList_append(fd_map, e); +//MSG("ADD ENTRY %d %s\n", e->fd, URL_STR(sd->url)); +} + +/* + * Remove and free entry from fd_map. + */ +static void Tls_fd_map_remove_entry(int fd) +{ + void *data = dList_find_custom(fd_map, INT2VOIDP(fd), Tls_fd_map_cmp); + +//MSG("REMOVE ENTRY %d\n", fd); + if (data) { + dList_remove_fast(fd_map, data); + dFree(data); + } else { + MSG("TLS FD ENTRY NOT FOUND FOR %d\n", fd); + } +} + +/* + * Return TLS connection information for a given file + * descriptor, or NULL if no TLS connection was found. + */ +void *a_Tls_connection(int fd) +{ + Conn_t *conn; + + if (fd_map) { + FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd), + Tls_fd_map_cmp); + + if (fme && (conn = a_Klist_get_data(conn_list, fme->connkey))) + return conn; + } + return NULL; +} + +/* + * Add a new TLS connection information node. + */ +static int Tls_conn_new(int fd, const DilloUrl *url, SSL *ssl) +{ + int key; + + Conn_t *conn = dNew0(Conn_t, 1); + conn->fd = fd; + conn->url = a_Url_dup(url); + conn->ssl = ssl; + conn->connecting = TRUE; + + key = a_Klist_insert(&conn_list, conn); + + Tls_fd_map_add_entry(fd, key); + + return key; +} + +/* + * Let's monitor for TLS alerts. + */ +static void Tls_info_cb(const SSL *ssl, int where, int ret) +{ + if (where & SSL_CB_ALERT) { + MSG("TLS ALERT on %s: %s\n", (where & SSL_CB_READ) ? "read" : "write", + SSL_alert_desc_string_long(ret)); + } +} + +/* + * Load trusted certificates. + * This is like using SSL_CTX_load_verify_locations() but permitting more + * than one bundle and more than one directory. Due to the notoriously + * abysmal openssl documentation, this was worked out from reading discussion + * on the web and then reading openssl source to see what it normally does. + */ +static void Tls_load_certificates() +{ + /* curl-7.37.1 says that the following bundle locations are used on "Debian + * systems", "Redhat and Mandriva", "old(er) Redhat", "FreeBSD", and + * "OpenBSD", respectively -- and that the /etc/ssl/certs/ path is needed on + * "SUSE". No doubt it's all changed some over time, but this gives us + * something to work with. + */ + uint_t u; + char *userpath; + static const char *ca_files[] = { + "/etc/ssl/certs/ca-certificates.crt", + "/etc/pki/tls/certs/ca-bundle.crt", + "/usr/share/ssl/certs/ca-bundle.crt", + "/usr/local/share/certs/ca-root.crt", + "/etc/ssl/cert.pem", + CA_CERTS_FILE + }; + + static const char *ca_paths[] = { + "/etc/ssl/certs/", + CA_CERTS_DIR + }; + + X509_STORE *store = SSL_CTX_get_cert_store(ssl_context); + X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); + + for (u = 0; u < sizeof(ca_files) / sizeof(ca_files[0]); u++) { + if (*ca_files[u]) + X509_LOOKUP_load_file(lookup, ca_files[u], X509_FILETYPE_PEM); + } + + lookup = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir()); + for (u = 0; u < sizeof(ca_paths)/sizeof(ca_paths[0]); u++) { + if (*ca_paths[u]) + X509_LOOKUP_add_dir(lookup, ca_paths[u], X509_FILETYPE_PEM); + } + + userpath = dStrconcat(dGethomedir(), "/.dillo/certs/", NULL); + X509_LOOKUP_add_dir(lookup, userpath, X509_FILETYPE_PEM); + dFree(userpath); + + /* Clear out errors in the queue (file not found, etc.) */ + while(ERR_get_error()) + ; +} + +/* + * Initialize the OpenSSL library. + */ +void a_Tls_init(void) +{ + SSL_library_init(); + SSL_load_error_strings(); + if (RAND_status() != 1) { + /* The standard solution is to provide it with more entropy, but this + * involves knowing very well that you are doing exactly the right thing. + */ + MSG_ERR("Disabling HTTPS: Insufficient entropy for openssl.\n"); + return; + } + + /* Create SSL context */ + ssl_context = SSL_CTX_new(SSLv23_client_method()); + if (ssl_context == NULL) { + MSG_ERR("Disabling HTTPS: Error creating SSL context.\n"); + return; + } + + SSL_CTX_set_info_callback(ssl_context, Tls_info_cb); + + /* Don't want: eNULL, which has no encryption; aNULL, which has no + * authentication; LOW, which as of 2014 use 64 or 56-bit encryption; + * EXPORT40, which uses 40-bit encryption; RC4, for which methods were + * found in 2013 to defeat it somewhat too easily. + */ + SSL_CTX_set_cipher_list(ssl_context, + "ALL:!aNULL:!eNULL:!LOW:!EXPORT40:!RC4"); + + /* SSL2 has been known to be insecure forever, disabling SSL3 is in response + * to POODLE, and disabling compression is in response to CRIME. + */ + SSL_CTX_set_options(ssl_context, + SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_COMPRESSION); + + /* This lets us deal with self-signed certificates */ + SSL_CTX_set_verify(ssl_context, SSL_VERIFY_NONE, NULL); + + Tls_load_certificates(); + + fd_map = dList_new(20); + servers = dList_new(8); +} + +/* + * Save certificate with a hashed filename. + * Return: 0 on success, 1 on failure. + */ +static int Tls_save_certificate_home(X509 * cert) +{ + char buf[4096]; + + FILE * fp = NULL; + uint_t i = 0; + int ret = 1; + + /* Attempt to create .dillo/certs blindly - check later */ + snprintf(buf, 4096, "%s/.dillo/", dGethomedir()); + mkdir(buf, 01777); + snprintf(buf, 4096, "%s/.dillo/certs/", dGethomedir()); + mkdir(buf, 01777); + + do { + snprintf(buf, 4096, "%s/.dillo/certs/%lx.%u", + dGethomedir(), X509_subject_name_hash(cert), i); + + fp=fopen(buf, "r"); + if (fp == NULL){ + /* File name doesn't exist so we can use it safely */ + fp=fopen(buf, "w"); + if (fp == NULL){ + MSG("Unable to open cert save file in home dir\n"); + break; + } else { + PEM_write_X509(fp, cert); + fclose(fp); + MSG("Wrote certificate\n"); + ret = 0; + break; + } + } else { + fclose(fp); + } + i++; + /* Don't loop too many times - just give up */ + } while (i < 1024); + + return ret; +} + +/* + * Test whether a URL corresponds to a server. + */ +static int Tls_servers_cmp(const void *v1, const void *v2) +{ + Server_t *s = (Server_t *)v1; + const DilloUrl *url = (const DilloUrl *)v2; + const char *host = URL_HOST(url); + int port = URL_PORT(url); + + return (dStrAsciiCasecmp(s->hostname, host) || (port != s->port)); +} + +/* + * The purpose here is to permit a single initial connection to a server. + * Once we have the certificate, know whether we like it -- and whether the + * user accepts it -- HTTP can run through queued sockets as normal. + * + * Return: 1 means yes, 0 means not yet, -1 means never. + * TODO: Something clearer or different. + */ +int a_Tls_connect_ready(const DilloUrl *url) +{ + Server_t *s; + int i, len; + const char *host = URL_HOST(url); + const int port = URL_PORT(url); + int ret = TLS_CONNECT_READY; + + dReturn_val_if_fail(ssl_context, TLS_CONNECT_NEVER); + + len = dList_length(servers); + + for (i = 0; i < len; i++) { + s = dList_nth_data(servers, i); + + if (!dStrAsciiCasecmp(s->hostname, host) && (port == s->port)) { + if (s->cert_status == CERT_STATUS_RECEIVING) + ret = TLS_CONNECT_NOT_YET; + else if (s->cert_status == CERT_STATUS_BAD) + ret = TLS_CONNECT_NEVER; + + if (s->cert_status == CERT_STATUS_NONE) + s->cert_status = CERT_STATUS_RECEIVING; + return ret; + } + } + s = dNew(Server_t, 1); + + s->port = port; + s->hostname = dStrdup(host); + s->cert_status = CERT_STATUS_RECEIVING; + dList_append(servers, s); + return ret; +} + +/* + * Did we find problems with the certificate, and did the user proceed to + * reject the connection? + */ +static int Tls_user_said_no(const DilloUrl *url) +{ + Server_t *s = dList_find_custom(servers, url, Tls_servers_cmp); + + if (!s) + return FALSE; + + return s->cert_status == CERT_STATUS_BAD; +} + +/* + * Did we find problems with the certificate, and did the user proceed to + * accept the connection anyway? + */ +static int Tls_user_said_yes(const DilloUrl *url) +{ + Server_t *s = dList_find_custom(servers, url, Tls_servers_cmp); + + if (!s) + return FALSE; + + return s->cert_status == CERT_STATUS_USER_ACCEPTED; +} + +/******************** BEGINNING OF STUFF DERIVED FROM wget-1.16.3 */ + +#define ASTERISK_EXCLUDES_DOT /* mandated by rfc2818 */ + +/* Return true is STRING (case-insensitively) matches PATTERN, false + otherwise. The recognized wildcard character is "*", which matches + any character in STRING except ".". Any number of the "*" wildcard + may be present in the pattern. + + This is used to match of hosts as indicated in rfc2818: "Names may + contain the wildcard character * which is considered to match any + single domain name component or component fragment. E.g., *.a.com + matches foo.a.com but not bar.foo.a.com. f*.com matches foo.com but + not bar.com [or foo.bar.com]." + + If the pattern contain no wildcards, pattern_match(a, b) is + equivalent to !strcasecmp(a, b). */ + +static bool_t pattern_match (const char *pattern, const char *string) +{ + + const char *p = pattern, *n = string; + char c; + for (; (c = tolower (*p++)) != '\0'; n++) + if (c == '*') + { + for (c = tolower (*p); c == '*'; c = tolower (*++p)) + ; + for (; *n != '\0'; n++) + if (tolower (*n) == c && pattern_match (p, n)) + return TRUE; +#ifdef ASTERISK_EXCLUDES_DOT + else if (*n == '.') + return FALSE; +#endif + return c == '\0'; + } + else + { + if (c != tolower (*n)) + return FALSE; + } + return *n == '\0'; +} + +static bool_t Tls_check_cert_hostname(X509 *cert, const DilloUrl *url, + int *choice) +{ + dReturn_val_if_fail(cert && url, -1); + + char *msg; + const char *host = URL_HOST(url); + GENERAL_NAMES *subjectAltNames; + bool_t success = TRUE, alt_name_checked = FALSE;; + char common_name[256]; + + /* Check that HOST matches the common name in the certificate. + #### The following remains to be done: + + - When matching against common names, it should loop over all + common names and choose the most specific one, i.e. the last + one, not the first one, which the current code picks. + + - Ensure that ASN1 strings from the certificate are encoded as + UTF-8 which can be meaningfully compared to HOST. */ + + subjectAltNames = X509_get_ext_d2i (cert, NID_subject_alt_name, NULL, NULL); + + if (subjectAltNames) + { + /* Test subject alternative names */ + + /* Do we want to check for dNSNAmes or ipAddresses (see RFC 2818)? + * Signal it by host_in_octet_string. */ + ASN1_OCTET_STRING *host_in_octet_string = a2i_IPADDRESS (host); + + int numaltnames = sk_GENERAL_NAME_num (subjectAltNames); + int i; + for (i=0; i < numaltnames; i++) + { + const GENERAL_NAME *name = + sk_GENERAL_NAME_value (subjectAltNames, i); + if (name) + { + if (host_in_octet_string) + { + if (name->type == GEN_IPADD) + { + /* Check for ipAddress */ + /* TODO: Should we convert between IPv4-mapped IPv6 + * addresses and IPv4 addresses? */ + alt_name_checked = TRUE; + if (!ASN1_STRING_cmp (host_in_octet_string, + name->d.iPAddress)) + break; + } + } + else if (name->type == GEN_DNS) + { + /* dNSName should be IA5String (i.e. ASCII), however who + * does trust CA? Convert it into UTF-8 for sure. */ + unsigned char *name_in_utf8 = NULL; + + /* Check for dNSName */ + alt_name_checked = TRUE; + + if (0 <= ASN1_STRING_to_UTF8 (&name_in_utf8, name->d.dNSName)) + { + /* Compare and check for NULL attack in ASN1_STRING */ + if (pattern_match ((char *)name_in_utf8, host) && + (strlen ((char *)name_in_utf8) == + (size_t)ASN1_STRING_length (name->d.dNSName))) + { + OPENSSL_free (name_in_utf8); + break; + } + OPENSSL_free (name_in_utf8); + } + } + } + } + sk_GENERAL_NAME_pop_free(subjectAltNames, GENERAL_NAME_free); + if (host_in_octet_string) + ASN1_OCTET_STRING_free(host_in_octet_string); + + if (alt_name_checked == TRUE && i >= numaltnames) + { + success = FALSE; + msg = dStrconcat("No certificate subject alternative name matches" + " requested host name \n", host, NULL); + *choice = a_Dialog_choice("Dillo TLS security warning", + msg, "Continue", "Cancel", NULL); + dFree(msg); + + switch (*choice){ + case 1: + success = TRUE; + break; + case 2: + break; + default: + break; + } + } + } + + if (alt_name_checked == FALSE) + { + /* Test commomName */ + X509_NAME *xname = X509_get_subject_name(cert); + common_name[0] = '\0'; + X509_NAME_get_text_by_NID (xname, NID_commonName, common_name, + sizeof (common_name)); + + if (!pattern_match (common_name, host)) + { + success = FALSE; + msg = dStrconcat("Certificate common name ", common_name, + " doesn't match requested host name ", host, NULL); + *choice = a_Dialog_choice("Dillo TLS security warning", + msg, "Continue", "Cancel", NULL); + dFree(msg); + + switch (*choice){ + case 1: + success = TRUE; + break; + case 2: + break; + default: + break; + } + } + else + { + /* We now determine the length of the ASN1 string. If it + * differs from common_name's length, then there is a \0 + * before the string terminates. This can be an instance of a + * null-prefix attack. + * + * https://www.blackhat.com/html/bh-usa-09/bh-usa-09-archives.html#Marlinspike + * */ + + int i = -1, j; + X509_NAME_ENTRY *xentry; + ASN1_STRING *sdata; + + if (xname) { + for (;;) + { + j = X509_NAME_get_index_by_NID (xname, NID_commonName, i); + if (j == -1) break; + i = j; + } + } + + xentry = X509_NAME_get_entry(xname,i); + sdata = X509_NAME_ENTRY_get_data(xentry); + if (strlen (common_name) != (size_t)ASN1_STRING_length (sdata)) + { + success = FALSE; + msg = dStrconcat("Certificate common name is invalid (contains a NUL " + "character). This may be an indication that the " + "host is not who it claims to be -- that is, not " + "the real ", host, NULL); + *choice = a_Dialog_choice("Dillo TLS security warning", + msg, "Continue", "Cancel", NULL); + dFree(msg); + + switch (*choice){ + case 1: + success = TRUE; + break; + case 2: + break; + default: + break; + } + } + } + } + return success; +} + +/******************** END OF STUFF DERIVED FROM wget-1.16.3 */ + +/* + * Examine the certificate, and, if problems are detected, ask the user what + * to do. + * Return: -1 if connection should be canceled, or 0 if it should continue. + */ +static int Tls_examine_certificate(SSL *ssl, const DilloUrl *url) +{ + X509 *remote_cert; + long st; + char buf[4096], *cn, *msg; + int choice = -1, ret = -1; + char *title = dStrconcat("Dillo TLS security warning: ",URL_HOST(url),NULL); + Server_t *srv = dList_find_custom(servers, url, Tls_servers_cmp); + + remote_cert = SSL_get_peer_certificate(ssl); + if (remote_cert == NULL){ + /* Inform user that remote system cannot be trusted */ + choice = a_Dialog_choice(title, + "The remote system is not presenting a certificate. " + "This site cannot be trusted. Sending data is not safe.", + "Continue", "Cancel", NULL); + + /* Abort on anything but "Continue" */ + if (choice == 1){ + ret = 0; + } + + } else if (Tls_check_cert_hostname(remote_cert, url, &choice)) { + /* Figure out if (and why) the remote system can't be trusted */ + st = SSL_get_verify_result(ssl); + switch (st) { + case X509_V_OK: + ret = 0; + break; + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + /* Either self signed and untrusted */ + /* Extract CN from certificate name information */ + if ((cn = strstr(remote_cert->name, "/CN=")) == NULL) { + strcpy(buf, "(no CN given)"); + } else { + char *cn_end; + + cn += 4; + + if ((cn_end = strstr(cn, "/")) == NULL ) + cn_end = cn + strlen(cn); + + strncpy(buf, cn, (size_t) (cn_end - cn)); + buf[cn_end - cn] = '\0'; + } + msg = dStrconcat("The remote certificate is self-signed and " + "untrusted. For address: ", buf, NULL); + choice = a_Dialog_choice(title, + msg, "Continue", "Cancel", "Save Certificate", NULL); + dFree(msg); + + switch (choice){ + case 1: + ret = 0; + break; + case 2: + break; + case 3: + /* Save certificate to a file here and recheck the chain */ + /* Potential security problems because we are writing + * to the filesystem */ + Tls_save_certificate_home(remote_cert); + ret = 1; + break; + default: + break; + } + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: + choice = a_Dialog_choice(title, + "The issuer for the remote certificate cannot be found. " + "The authenticity of the remote certificate cannot be trusted.", + "Continue", "Cancel", NULL); + + if (choice == 1) { + ret = 0; + } + break; + + case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: + case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: + case X509_V_ERR_CERT_SIGNATURE_FAILURE: + case X509_V_ERR_CRL_SIGNATURE_FAILURE: + choice = a_Dialog_choice(title, + "The remote certificate signature could not be read " + "or is invalid and should not be trusted", + "Continue", "Cancel", NULL); + + if (choice == 1) { + ret = 0; + } + break; + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_CRL_NOT_YET_VALID: + choice = a_Dialog_choice(title, + "Part of the remote certificate is not yet valid. " + "Certificates usually have a range of dates over which " + "they are to be considered valid, and the certificate " + "presented has a starting validity after today's date " + "You should be cautious about using this site", + "Continue", "Cancel", NULL); + + if (choice == 1) { + ret = 0; + } + break; + case X509_V_ERR_CERT_HAS_EXPIRED: + case X509_V_ERR_CRL_HAS_EXPIRED: + choice = a_Dialog_choice(title, + "The remote certificate has expired. The certificate " + "wasn't designed to last this long. You should avoid " + "this site.", + "Continue", "Cancel", NULL); + if (choice == 1) { + ret = 0; + } + break; + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: + case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: + choice = a_Dialog_choice(title, + "There was an error in the certificate presented. " + "Some of the certificate data was improperly formatted " + "making it impossible to determine if the certificate " + "is valid. You should not trust this certificate.", + "Continue", "Cancel", NULL); + if (choice == 1) { + ret = 0; + } + break; + case X509_V_ERR_INVALID_CA: + case X509_V_ERR_INVALID_PURPOSE: + case X509_V_ERR_CERT_UNTRUSTED: + case X509_V_ERR_CERT_REJECTED: + case X509_V_ERR_KEYUSAGE_NO_CERTSIGN: + choice = a_Dialog_choice(title, + "One of the certificates in the chain is being used " + "incorrectly (possibly due to configuration problems " + "with the remote system. The connection should not " + "be trusted", + "Continue", "Cancel", NULL); + if (choice == 1) { + ret = 0; + } + break; + case X509_V_ERR_SUBJECT_ISSUER_MISMATCH: + case X509_V_ERR_AKID_SKID_MISMATCH: + case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH: + choice = a_Dialog_choice(title, + "Some of the information presented by the remote system " + "does not match other information presented. " + "This may be an attempt to eavesdrop on communications", + "Continue", "Cancel", NULL); + if (choice == 1) { + ret = 0; + } + break; + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + choice = a_Dialog_choice(title, + "Self signed certificate in certificate chain. The certificate " + "chain could be built up using the untrusted certificates but the " + "root could not be found locally.", + "Continue", "Cancel", NULL); + if (choice == 1) { + ret = 0; + } + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + choice = a_Dialog_choice(title, + "Unable to get local issuer certificate. The issuer certificate " + "of an untrusted certificate cannot be found.", + "Continue", "Cancel", NULL); + if (choice == 1) { + ret = 0; + } + break; + default: /* Need to add more options later */ + snprintf(buf, 80, + "The remote certificate cannot be verified (code %ld)", st); + choice = a_Dialog_choice(title, + buf, "Continue", "Cancel", NULL); + /* abort on anything but "Continue" */ + if (choice == 1){ + ret = 0; + } + } + X509_free(remote_cert); + remote_cert = 0; + } + dFree(title); + + if (choice == 2) + srv->cert_status = CERT_STATUS_BAD; + else if (choice == -1) + srv->cert_status = CERT_STATUS_GOOD; + else + srv->cert_status = CERT_STATUS_USER_ACCEPTED; + + return ret; +} + +/* + * If the connection was closed before we got the certificate, we need to + * reset state so that we'll try again. + */ +void a_Tls_reset_server_state(const DilloUrl *url) +{ + if (servers) { + Server_t *s = dList_find_custom(servers, url, Tls_servers_cmp); + + if (s && s->cert_status == CERT_STATUS_RECEIVING) + s->cert_status = CERT_STATUS_NONE; + } +} + +/* + * Close an open TLS connection. + */ +static void Tls_close_by_key(int connkey) +{ + Conn_t *c; + + if ((c = a_Klist_get_data(conn_list, connkey))) { + a_Tls_reset_server_state(c->url); + if (c->connecting) { + a_IOwatch_remove_fd(c->fd, -1); + dClose(c->fd); + } + SSL_shutdown(c->ssl); + SSL_free(c->ssl); + + a_Url_free(c->url); + Tls_fd_map_remove_entry(c->fd); + a_Klist_remove(conn_list, connkey); + dFree(c); + } +} + +/* + * Connect, set a callback if it's still not completed. If completed, check + * the certificate and report back to http. + */ +static void Tls_connect(int fd, int connkey) +{ + int ret; + bool_t ongoing = FALSE, failed = TRUE; + Conn_t *conn; + + if (!(conn = a_Klist_get_data(conn_list, connkey))) { + MSG("Tls_connect: conn for fd %d not valid\n", fd); + return; + } + + assert(!ERR_get_error()); + + ret = SSL_connect(conn->ssl); + + if (ret <= 0) { + int err1_ret = SSL_get_error(conn->ssl, ret); + if (err1_ret == SSL_ERROR_WANT_READ || + err1_ret == SSL_ERROR_WANT_WRITE) { + int want = err1_ret == SSL_ERROR_WANT_READ ? DIO_READ : DIO_WRITE; + + _MSG("iowatching fd %d for tls -- want %s\n", fd, + err1_ret == SSL_ERROR_WANT_READ ? "read" : "write"); + a_IOwatch_remove_fd(fd, -1); + a_IOwatch_add_fd(fd, want, Tls_connect_cb, INT2VOIDP(connkey)); + ongoing = TRUE; + failed = FALSE; + } else if (err1_ret == SSL_ERROR_SYSCALL || err1_ret == SSL_ERROR_SSL) { + unsigned long err2_ret = ERR_get_error(); + + if (err2_ret) { + do { + MSG("SSL_connect() failed: %s\n", + ERR_error_string(err2_ret, NULL)); + } while ((err2_ret = ERR_get_error())); + } else { + /* nothing in the error queue */ + if (ret == 0) { + MSG("TLS connect error: \"an EOF was observed that violates " + "the protocol\"\n"); + /* + * I presume we took too long on our side and the server grew + * impatient. + */ + } else if (ret == -1) { + MSG("TLS connect error: %s\n", dStrerror(errno)); + + /* If the following can happen, I'll add code to handle it, but + * I don't want to add code blindly if it isn't getting used + */ + assert(errno != EAGAIN && errno != EINTR); + } else { + MSG_ERR("According to the man page for SSL_get_error(), this " + "was not a possibility (ret %d).\n", ret); + } + } + } else { + MSG("SSL_get_error() returned %d on a connect.\n", err1_ret); + } + } else { + if (Tls_user_said_yes(conn->url) || + (Tls_examine_certificate(conn->ssl, conn->url) != -1)) + failed = FALSE; + } + + /* + * If there were problems with the certificate, the connection may have + * been closed by the server if the user responded too slowly to a popup. + */ + + if (!ongoing) { + if (a_Klist_get_data(conn_list, connkey)) { + conn->connecting = FALSE; + if (failed) { + Tls_close_by_key(connkey); + } + a_IOwatch_remove_fd(fd, DIO_READ|DIO_WRITE); + a_Http_connect_done(fd, failed ? FALSE : TRUE); + } else { + MSG("Connection disappeared. Too long with a popup popped up?\n"); + } + } +} + +static void Tls_connect_cb(int fd, void *vconnkey) +{ + Tls_connect(fd, VOIDP2INT(vconnkey)); +} + +/* + * Perform the TLS handshake on an open socket. + */ +void a_Tls_handshake(int fd, const DilloUrl *url) +{ + SSL *ssl; + bool_t success = TRUE; + int connkey = -1; + + if (!ssl_context) + success = FALSE; + + if (success && Tls_user_said_no(url)) { + success = FALSE; + } + + assert(!ERR_get_error()); + + if (success && !(ssl = SSL_new(ssl_context))) { + unsigned long err_ret = ERR_get_error(); + do { + MSG("SSL_new() failed: %s\n", ERR_error_string(err_ret, NULL)); + } while ((err_ret = ERR_get_error())); + success = FALSE; + } + + /* assign TLS connection to this file descriptor */ + if (success && !SSL_set_fd(ssl, fd)) { + unsigned long err_ret = ERR_get_error(); + do { + MSG("SSL_set_fd() failed: %s\n", ERR_error_string(err_ret, NULL)); + } while ((err_ret = ERR_get_error())); + success = FALSE; + } + + if (success) + connkey = Tls_conn_new(fd, url, ssl); + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + /* Server Name Indication. From the openssl changelog, it looks like this + * came along in 2010. + */ + if (success && !a_Url_host_is_ip(URL_HOST(url))) + SSL_set_tlsext_host_name(ssl, URL_HOST(url)); +#endif + + if (!success) { + a_Tls_reset_server_state(url); + a_Http_connect_done(fd, success); + } else { + Tls_connect(fd, connkey); + } +} + +/* + * Read data from an open TLS connection. + */ +int a_Tls_read(void *conn, void *buf, size_t len) +{ + Conn_t *c = (Conn_t*)conn; + return SSL_read(c->ssl, buf, len); +} + +/* + * Write data to an open TLS connection. + */ +int a_Tls_write(void *conn, void *buf, size_t len) +{ + Conn_t *c = (Conn_t*)conn; + return SSL_write(c->ssl, buf, len); +} + +void a_Tls_close_by_fd(int fd) +{ + FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd), + Tls_fd_map_cmp); + + if (fme) { + Tls_close_by_key(fme->connkey); + } +} + +static void Tls_servers_freeall() +{ + if (servers) { + Server_t *s; + int i, n = dList_length(servers); + + for (i = 0; i < n; i++) { + s = (Server_t *) dList_nth_data(servers, i); + dFree(s->hostname); + dFree(s); + } + dList_free(servers); + } +} + +static void Tls_fd_map_remove_all() +{ + if (fd_map) { + FdMapEntry_t *fme; + int i, n = dList_length(fd_map); + + for (i = 0; i < n; i++) { + fme = (FdMapEntry_t *) dList_nth_data(fd_map, i); + dFree(fme); + } + dList_free(fd_map); + } +} + +/* + * Clean up the OpenSSL library + */ +void a_Tls_freeall(void) +{ + if (ssl_context) + SSL_CTX_free(ssl_context); + Tls_fd_map_remove_all(); + Tls_servers_freeall(); +} + +#endif /* ENABLE_SSL */ diff --git a/src/IO/tls.h b/src/IO/tls.h new file mode 100644 index 00000000..e3892cb2 --- /dev/null +++ b/src/IO/tls.h @@ -0,0 +1,47 @@ +#ifndef __TLS_H__ +#define __TLS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../url.h" + +#define TLS_CONNECT_NEVER -1 +#define TLS_CONNECT_NOT_YET 0 +#define TLS_CONNECT_READY 1 + +void a_Tls_init(); + + +#ifdef ENABLE_SSL +int a_Tls_connect_ready(const DilloUrl *url); +void a_Tls_reset_server_state(const DilloUrl *url); + +/* Use to initiate a TLS connection. */ +void a_Tls_handshake(int fd, const DilloUrl *url); + +void *a_Tls_connection(int fd); + +void a_Tls_freeall(); + +void a_Tls_close_by_fd(int fd); +int a_Tls_read(void *conn, void *buf, size_t len); +int a_Tls_write(void *conn, void *buf, size_t len); +#else + +#define a_Tls_connect_ready(url) TLS_CONNECT_NEVER +#define a_Tls_reset_server_state(url) ; +#define a_Tls_handshake(fd, url) ; +#define a_Tls_connection(fd) NULL +#define a_Tls_freeall() ; +#define a_Tls_close_by_fd(fd) ; +#define a_Tls_read(conn, buf, len) 0 +#define a_Tls_write(conn, buf, len) 0 +#endif +#ifdef __cplusplus +} +#endif + +#endif /* __TLS_H__ */ + diff --git a/src/dillo.cc b/src/dillo.cc index 2bfab238..6e28f155 100644 --- a/src/dillo.cc +++ b/src/dillo.cc @@ -45,7 +45,7 @@ #include "dns.h" #include "web.hh" -#include "IO/ssl.h" +#include "IO/tls.h" #include "IO/Url.h" #include "IO/mime.h" #include "capi.h" @@ -477,7 +477,7 @@ int main(int argc, char **argv) a_Dns_init(); a_Web_init(); a_Http_init(); - a_Ssl_init(); + a_Tls_init(); a_Mime_init(); a_Capi_init(); a_Dicache_init(); @@ -599,7 +599,7 @@ int main(int argc, char **argv) a_Cache_freeall(); a_Dicache_freeall(); a_Http_freeall(); - a_Ssl_freeall(); + a_Tls_freeall(); a_Dns_freeall(); a_History_freeall(); a_Prefs_freeall(); -- cgit v1.2.3 From fcee2570a762a3fe7aecf714188c964f35820d73 Mon Sep 17 00:00:00 2001 From: corvid Date: Thu, 28 May 2015 18:53:58 +0000 Subject: update docs a bit --- dillorc | 2 +- doc/dillo.1.in | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dillorc b/dillorc index 9c783a03..18d52dd0 100644 --- a/dillorc +++ b/dillorc @@ -189,7 +189,7 @@ search_url="Google http://www.google.com/search?ie=UTF-8&oe=UTF-8&q=%s" # page/image/stylesheet. #http_persistent_conns=NO -# Set the proxy information for http. +# Set the proxy information for http/https. # Note that the http_proxy environment variable overrides this setting. # WARNING: FTP and downloads plugins use wget. To use a proxy with them, # you will need to configure wget accordingly. See diff --git a/doc/dillo.1.in b/doc/dillo.1.in index 3bb5fe03..f86d050c 100644 --- a/doc/dillo.1.in +++ b/doc/dillo.1.in @@ -11,7 +11,7 @@ dillo \- web browser Dillo is a lightweight graphical web browser that aims to be secure. It handles HTTP internally, and FILE, FTP, and DATA URIs are handled through a plugin system (dpi). In addition, -.I INSECURE +.I EXPERIMENTAL HTTPS support can be enabled. Both FTP and Dillo's download manager use the .BR wget (1) downloader. @@ -68,7 +68,7 @@ Error in command line arguments. User's home directory. .TP .B http_proxy -URL of proxy to send HTTP traffic through. +URL of proxy to send HTTP/HTTPS traffic through. .SH FILES .TP .I dpid -- cgit v1.2.3 From a6408e70f9b4ddc942490cfbe1822f17cd1a2317 Mon Sep 17 00:00:00 2001 From: corvid Date: Thu, 28 May 2015 18:55:25 +0000 Subject: cookies is_ssl -> is_tls --- dpi/cookies.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/dpi/cookies.c b/dpi/cookies.c index b858bd53..51767241 100644 --- a/dpi/cookies.c +++ b/dpi/cookies.c @@ -1142,14 +1142,14 @@ static int Cookies_set(char *cookie_string, char *url_host, * Compare the cookie with the supplied data to see whether it matches */ static bool_t Cookies_match(CookieData_t *cookie, const char *url_path, - bool_t host_only_val, bool_t is_ssl) + bool_t host_only_val, bool_t is_tls) { if (cookie->host_only != host_only_val) return FALSE; /* Insecure cookies match both secure and insecure urls, secure cookies match only secure urls */ - if (cookie->secure && !is_ssl) + if (cookie->secure && !is_tls) return FALSE; if (!Cookies_path_matches(url_path, cookie->path)) @@ -1163,7 +1163,7 @@ static void Cookies_add_matching_cookies(const char *domain, const char *url_path, bool_t host_only_val, Dlist *matching_cookies, - bool_t is_ssl) + bool_t is_tls) { DomainNode *node = dList_find_sorted(domains, domain, Domain_node_by_domain_cmp); @@ -1183,7 +1183,7 @@ static void Cookies_add_matching_cookies(const char *domain, --i; continue; } /* Check if the cookie matches the requesting URL */ - if (Cookies_match(cookie, url_path, host_only_val, is_ssl)) { + if (Cookies_match(cookie, url_path, host_only_val, is_tls)) { int j; CookieData_t *curr; uint_t path_length = strlen(cookie->path); @@ -1213,7 +1213,7 @@ static char *Cookies_get(char *url_host, char *url_path, char *domain_str, *str; CookieData_t *cookie; Dlist *matching_cookies; - bool_t is_ssl, is_ip_addr, host_only_val; + bool_t is_tls, is_ip_addr, host_only_val; Dstr *cookie_dstring; int i; @@ -1224,7 +1224,7 @@ static char *Cookies_get(char *url_host, char *url_path, matching_cookies = dList_new(8); /* Check if the protocol is secure or not */ - is_ssl = (!dStrAsciiCasecmp(url_scheme, "https")); + is_tls = (!dStrAsciiCasecmp(url_scheme, "https")); is_ip_addr = Cookies_domain_is_ip(url_host); @@ -1240,17 +1240,17 @@ static char *Cookies_get(char *url_host, char *url_path, /* e.g., sub.example.com set a cookie with domain ".sub.example.com". */ domain_str = dStrconcat(".", url_host, NULL); Cookies_add_matching_cookies(domain_str, url_path, host_only_val, - matching_cookies, is_ssl); + matching_cookies, is_tls); dFree(domain_str); } host_only_val = TRUE; /* e.g., sub.example.com set a cookie with no domain attribute. */ Cookies_add_matching_cookies(url_host, url_path, host_only_val, - matching_cookies, is_ssl); + matching_cookies, is_tls); host_only_val = FALSE; /* e.g., sub.example.com set a cookie with domain "sub.example.com". */ Cookies_add_matching_cookies(url_host, url_path, host_only_val, - matching_cookies, is_ssl); + matching_cookies, is_tls); if (!is_ip_addr) { for (domain_str = strchr(url_host+1, '.'); @@ -1258,12 +1258,12 @@ static char *Cookies_get(char *url_host, char *url_path, domain_str = strchr(domain_str+1, '.')) { /* e.g., sub.example.com set a cookie with domain ".example.com". */ Cookies_add_matching_cookies(domain_str, url_path, host_only_val, - matching_cookies, is_ssl); + matching_cookies, is_tls); if (domain_str[1]) { domain_str++; /* e.g., sub.example.com set a cookie with domain "example.com".*/ Cookies_add_matching_cookies(domain_str, url_path, host_only_val, - matching_cookies, is_ssl); + matching_cookies, is_tls); } } } -- cgit v1.2.3 From 485d42b5676ce9d324063f99312c1389941ea41e Mon Sep 17 00:00:00 2001 From: corvid Date: Thu, 28 May 2015 21:15:21 +0000 Subject: libpng 1.6 series works for me --- configure.ac | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index aee37a47..be9c9c42 100644 --- a/configure.ac +++ b/configure.ac @@ -219,7 +219,10 @@ if test "x$enable_png" = "xyes"; then dnl Check if the user hasn't set the variable $PNG_CONFIG if test -z "$PNG_CONFIG"; then - PNG_CONFIG=`which libpng14-config` + PNG_CONFIG=`which libpng16-config` + if test -z "$PNG_CONFIG"; then + PNG_CONFIG=`which libpng14-config` + fi if test -z "$PNG_CONFIG"; then PNG_CONFIG=`which libpng12-config` fi @@ -245,7 +248,7 @@ dnl For debugging and to be user friendly AC_MSG_CHECKING([for libpng version]) png_version=`$PNG_CONFIG --version` case $png_version in - 1.[[024]].*) AC_MSG_RESULT([$png_version]) ;; + 1.[[0246]].*) AC_MSG_RESULT([$png_version]) ;; *) AC_MSG_RESULT([$png_version (unrecognised version)]) ;; esac -- cgit v1.2.3 From 73af306ef03f9ecc8b3213755f0ae830caf649c4 Mon Sep 17 00:00:00 2001 From: corvid Date: Fri, 29 May 2015 16:44:15 +0000 Subject: _MSG_ERR unused, unneeded, because it doesn't make much sense --- dpid/dpid_common.h | 1 - lout/msg.h | 1 - 2 files changed, 2 deletions(-) diff --git a/dpid/dpid_common.h b/dpid/dpid_common.h index cc7505a9..6df55ae1 100644 --- a/dpid/dpid_common.h +++ b/dpid/dpid_common.h @@ -18,7 +18,6 @@ */ #define _MSG(...) #define MSG(...) printf("[dpid]: " __VA_ARGS__) -#define _MSG_ERR(...) #define MSG_ERR(...) fprintf(stderr, "[dpid]: " __VA_ARGS__) #define dotDILLO_DPI ".dillo/dpi" diff --git a/lout/msg.h b/lout/msg.h index 4993c105..e52ff986 100644 --- a/lout/msg.h +++ b/lout/msg.h @@ -13,7 +13,6 @@ */ #define _MSG(...) #define _MSG_WARN(...) -#define _MSG_ERR(...) #define MSG(...) \ -- cgit v1.2.3 From 1970b812237a507a95394be1f31d3caa3242020b Mon Sep 17 00:00:00 2001 From: corvid Date: Fri, 29 May 2015 21:47:28 +0000 Subject: some more information for TLS warning popups --- src/IO/tls.c | 85 ++++++++++++++++++++++++++++++++++++++++++++++------------- src/dialog.cc | 20 +++++++------- 2 files changed, 77 insertions(+), 28 deletions(-) diff --git a/src/IO/tls.c b/src/IO/tls.c index 39252635..b16c2ed1 100644 --- a/src/IO/tls.c +++ b/src/IO/tls.c @@ -493,6 +493,10 @@ static bool_t Tls_check_cert_hostname(X509 *cert, const DilloUrl *url, { /* Test subject alternative names */ + Dstr *err = dStr_new(""); + dStr_sprintf(err, "Hostname %s does not match any of certificate's " + "Subject Alternative Names: ", host); + /* Do we want to check for dNSNAmes or ipAddresses (see RFC 2818)? * Signal it by host_in_octet_string. */ ASN1_OCTET_STRING *host_in_octet_string = a2i_IPADDRESS (host); @@ -516,6 +520,7 @@ static bool_t Tls_check_cert_hostname(X509 *cert, const DilloUrl *url, if (!ASN1_STRING_cmp (host_in_octet_string, name->d.iPAddress)) break; + dStr_sprintfa(err, "%s ", name->d.iPAddress); } } else if (name->type == GEN_DNS) @@ -537,6 +542,7 @@ static bool_t Tls_check_cert_hostname(X509 *cert, const DilloUrl *url, OPENSSL_free (name_in_utf8); break; } + dStr_sprintfa(err, "%s ", name_in_utf8); OPENSSL_free (name_in_utf8); } } @@ -549,11 +555,8 @@ static bool_t Tls_check_cert_hostname(X509 *cert, const DilloUrl *url, if (alt_name_checked == TRUE && i >= numaltnames) { success = FALSE; - msg = dStrconcat("No certificate subject alternative name matches" - " requested host name \n", host, NULL); *choice = a_Dialog_choice("Dillo TLS security warning", - msg, "Continue", "Cancel", NULL); - dFree(msg); + err->str, "Continue", "Cancel", NULL); switch (*choice){ case 1: @@ -565,6 +568,7 @@ static bool_t Tls_check_cert_hostname(X509 *cert, const DilloUrl *url, break; } } + dStr_free(err, 1); } if (alt_name_checked == FALSE) @@ -647,6 +651,46 @@ static bool_t Tls_check_cert_hostname(X509 *cert, const DilloUrl *url, /******************** END OF STUFF DERIVED FROM wget-1.16.3 */ +/* + * Get the certificate at the end of the chain, or NULL on failure. + * + * Rumor has it that the stack can be NULL if a connection has been reused + * and that the stack can then be reconstructed if necessary, but it doesn't + * sound like a case we'll encounter. + */ +static X509 *Tls_get_end_of_chain(SSL *ssl) +{ + STACK_OF(X509) *sk = SSL_get_peer_cert_chain(ssl); + + return sk ? sk_X509_value(sk, sk_X509_num(sk) - 1) : NULL; +} + +static void Tls_get_issuer_name(X509 *cert, char *buf, uint_t buflen) +{ + if (cert) { + X509_NAME_oneline(X509_get_issuer_name(cert), buf, buflen); + } else { + strncpy(buf, "(unknown)", buflen); + buf[buflen-1] = '\0'; + } +} + +static void Tls_get_expiration_str(X509 *cert, char *buf, uint_t buflen) +{ + ASN1_TIME *exp_date = X509_get_notAfter(cert); + BIO *b = BIO_new(BIO_s_mem()); + int rc = ASN1_TIME_print(b, exp_date); + + if (rc > 0) { + rc = BIO_gets(b, buf, buflen); + } + if (rc <= 0) { + strncpy(buf, "(unknown)", buflen); + buf[buflen-1] = '\0'; + } + BIO_free(b); +} + /* * Examine the certificate, and, if problems are detected, ask the user what * to do. @@ -656,7 +700,8 @@ static int Tls_examine_certificate(SSL *ssl, const DilloUrl *url) { X509 *remote_cert; long st; - char buf[4096], *cn, *msg; + const uint_t buflen = 4096; + char buf[buflen], *cn, *msg; int choice = -1, ret = -1; char *title = dStrconcat("Dillo TLS security warning: ",URL_HOST(url),NULL); Server_t *srv = dList_find_custom(servers, url, Tls_servers_cmp); @@ -761,14 +806,15 @@ static int Tls_examine_certificate(SSL *ssl, const DilloUrl *url) break; case X509_V_ERR_CERT_HAS_EXPIRED: case X509_V_ERR_CRL_HAS_EXPIRED: - choice = a_Dialog_choice(title, - "The remote certificate has expired. The certificate " - "wasn't designed to last this long. You should avoid " - "this site.", - "Continue", "Cancel", NULL); + Tls_get_expiration_str(remote_cert, buf, buflen); + msg = dStrconcat("The remote certificate expired on: ", buf, + ". This site can no longer be trusted.", NULL); + + choice = a_Dialog_choice(title, msg, "Continue", "Cancel", NULL); if (choice == 1) { ret = 0; } + dFree(msg); break; case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: @@ -812,23 +858,24 @@ static int Tls_examine_certificate(SSL *ssl, const DilloUrl *url) } break; case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: - choice = a_Dialog_choice(title, - "Self signed certificate in certificate chain. The certificate " - "chain could be built up using the untrusted certificates but the " - "root could not be found locally.", - "Continue", "Cancel", NULL); + Tls_get_issuer_name(Tls_get_end_of_chain(ssl), buf, buflen); + msg = dStrconcat("Certificate chain led to a self-signed certificate " + "instead of a trusted root. Name: ", buf , NULL); + choice = a_Dialog_choice(title, msg, "Continue", "Cancel", NULL); if (choice == 1) { ret = 0; } + dFree(msg); break; case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: - choice = a_Dialog_choice(title, - "Unable to get local issuer certificate. The issuer certificate " - "of an untrusted certificate cannot be found.", - "Continue", "Cancel", NULL); + Tls_get_issuer_name(Tls_get_end_of_chain(ssl), buf, buflen); + msg = dStrconcat("The issuer certificate of an untrusted certificate " + "cannot be found. Issuer: ", buf, NULL); + choice = a_Dialog_choice(title, msg, "Continue", "Cancel", NULL); if (choice == 1) { ret = 0; } + dFree(msg); break; default: /* Need to add more options later */ snprintf(buf, 80, diff --git a/src/dialog.cc b/src/dialog.cc index 10988c98..03949a1c 100644 --- a/src/dialog.cc +++ b/src/dialog.cc @@ -325,6 +325,7 @@ static void choice_cb(Fl_Widget *button, void *number) { choice_answer = VOIDP2INT(number); _MSG("choice_cb: %d\n", choice_answer); + button->window()->hide(); } @@ -358,16 +359,15 @@ int a_Dialog_choice(const char *title, const char *msg, ...) Fl_Window *window = new Fl_Window(ww, wh, title); window->set_modal(); window->begin(); - Fl_Group *ib = new Fl_Group(0, 0, window->w(), window->h()); - ib->begin(); - window->resizable(ib); - if (msg != NULL){ - Fl_Box *box = new Fl_Box(0, 0, ww, wh - bh, msg); - box->labelfont(FL_HELVETICA); - box->labelsize(14); - box->align(FL_ALIGN_WRAP); - } + Fl_Text_Buffer *buf = new Fl_Text_Buffer(); + buf->text(msg); + Fl_Text_Display *td = new Fl_Text_Display(0, 0, ww, wh - bh); + td->buffer(buf); + td->textsize((int) rint(14.0 * prefs.font_factor)); + td->wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 0); + + window->resizable(td); int xpos = gap; va_start(ap, msg); @@ -386,6 +386,8 @@ int a_Dialog_choice(const char *title, const char *msg, ...) while (window->shown()) Fl::wait(); _MSG("Dialog_choice answer = %d\n", answer); + td->buffer(NULL); + delete buf; delete window; return choice_answer; -- cgit v1.2.3 From 5b317d03a17331f400d93fb957216030a8c86fd2 Mon Sep 17 00:00:00 2001 From: corvid Date: Fri, 29 May 2015 22:14:32 +0000 Subject: documentation and not-currently-possible error case --- src/IO/tls.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/IO/tls.c b/src/IO/tls.c index b16c2ed1..dbdb1c92 100644 --- a/src/IO/tls.c +++ b/src/IO/tls.c @@ -466,10 +466,16 @@ static bool_t pattern_match (const char *pattern, const char *string) return *n == '\0'; } +/* + * Check that the certificate corresponds to the site it's presented for. + * + * Return TRUE if the hostname matched or the user indicated acceptance. + * FALSE on failure. + */ static bool_t Tls_check_cert_hostname(X509 *cert, const DilloUrl *url, int *choice) { - dReturn_val_if_fail(cert && url, -1); + dReturn_val_if_fail(cert && url, FALSE); char *msg; const char *host = URL_HOST(url); -- cgit v1.2.3 From 3d8b68e1bfde841e6d994d830dbc922256a6b7cf Mon Sep 17 00:00:00 2001 From: corvid Date: Fri, 29 May 2015 23:57:53 +0000 Subject: print out TLS version and cipher agreed upon after first connection with server --- src/IO/tls.c | 42 +++++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/src/IO/tls.c b/src/IO/tls.c index dbdb1c92..bab36644 100644 --- a/src/IO/tls.c +++ b/src/IO/tls.c @@ -407,20 +407,6 @@ static int Tls_user_said_no(const DilloUrl *url) return s->cert_status == CERT_STATUS_BAD; } -/* - * Did we find problems with the certificate, and did the user proceed to - * accept the connection anyway? - */ -static int Tls_user_said_yes(const DilloUrl *url) -{ - Server_t *s = dList_find_custom(servers, url, Tls_servers_cmp); - - if (!s) - return FALSE; - - return s->cert_status == CERT_STATUS_USER_ACCEPTED; -} - /******************** BEGINNING OF STUFF DERIVED FROM wget-1.16.3 */ #define ASTERISK_EXCLUDES_DOT /* mandated by rfc2818 */ @@ -472,13 +458,12 @@ static bool_t pattern_match (const char *pattern, const char *string) * Return TRUE if the hostname matched or the user indicated acceptance. * FALSE on failure. */ -static bool_t Tls_check_cert_hostname(X509 *cert, const DilloUrl *url, +static bool_t Tls_check_cert_hostname(X509 *cert, const char *host, int *choice) { - dReturn_val_if_fail(cert && url, FALSE); + dReturn_val_if_fail(cert && host, FALSE); char *msg; - const char *host = URL_HOST(url); GENERAL_NAMES *subjectAltNames; bool_t success = TRUE, alt_name_checked = FALSE;; char common_name[256]; @@ -702,15 +687,14 @@ static void Tls_get_expiration_str(X509 *cert, char *buf, uint_t buflen) * to do. * Return: -1 if connection should be canceled, or 0 if it should continue. */ -static int Tls_examine_certificate(SSL *ssl, const DilloUrl *url) +static int Tls_examine_certificate(SSL *ssl, Server_t *srv,const char *host) { X509 *remote_cert; long st; const uint_t buflen = 4096; char buf[buflen], *cn, *msg; int choice = -1, ret = -1; - char *title = dStrconcat("Dillo TLS security warning: ",URL_HOST(url),NULL); - Server_t *srv = dList_find_custom(servers, url, Tls_servers_cmp); + char *title = dStrconcat("Dillo TLS security warning: ", host, NULL); remote_cert = SSL_get_peer_certificate(ssl); if (remote_cert == NULL){ @@ -725,7 +709,7 @@ static int Tls_examine_certificate(SSL *ssl, const DilloUrl *url) ret = 0; } - } else if (Tls_check_cert_hostname(remote_cert, url, &choice)) { + } else if (Tls_check_cert_hostname(remote_cert, host, &choice)) { /* Figure out if (and why) the remote system can't be trusted */ st = SSL_get_verify_result(ssl); switch (st) { @@ -1009,9 +993,21 @@ static void Tls_connect(int fd, int connkey) MSG("SSL_get_error() returned %d on a connect.\n", err1_ret); } } else { - if (Tls_user_said_yes(conn->url) || - (Tls_examine_certificate(conn->ssl, conn->url) != -1)) + Server_t *srv = dList_find_custom(servers, conn->url, Tls_servers_cmp); + + if (srv->cert_status == CERT_STATUS_RECEIVING) { + /* Making first connection with the server */ + const char *version = SSL_get_version(conn->ssl); + const SSL_CIPHER *cipher = SSL_get_current_cipher(conn->ssl); + + MSG("%s: %s, cipher %s\n", URL_AUTHORITY(conn->url), version, + SSL_CIPHER_get_name(cipher)); + } + + if (srv->cert_status == CERT_STATUS_USER_ACCEPTED || + (Tls_examine_certificate(conn->ssl, srv, URL_HOST(conn->url))!=-1)) { failed = FALSE; + } } /* -- cgit v1.2.3 From d11a250d5739d106215d66700af3fc530566b604 Mon Sep 17 00:00:00 2001 From: corvid Date: Sat, 30 May 2015 00:07:13 +0000 Subject: let's not print tls alerts for 'close notify' --- src/IO/tls.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/IO/tls.c b/src/IO/tls.c index bab36644..2be2ebc0 100644 --- a/src/IO/tls.c +++ b/src/IO/tls.c @@ -185,8 +185,11 @@ static int Tls_conn_new(int fd, const DilloUrl *url, SSL *ssl) static void Tls_info_cb(const SSL *ssl, int where, int ret) { if (where & SSL_CB_ALERT) { - MSG("TLS ALERT on %s: %s\n", (where & SSL_CB_READ) ? "read" : "write", - SSL_alert_desc_string_long(ret)); + const char *str = SSL_alert_desc_string_long(ret); + + if (strcmp(str, "close notify")) + MSG("TLS ALERT on %s: %s\n", (where & SSL_CB_READ) ? "read" : "write", + str); } } -- cgit v1.2.3 From 608d1a61b202814080d680f9a1c33e72a926ae8c Mon Sep 17 00:00:00 2001 From: corvid Date: Sat, 30 May 2015 16:42:37 +0000 Subject: print certificate chain --- src/IO/tls.c | 48 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/IO/tls.c b/src/IO/tls.c index 2be2ebc0..9368d563 100644 --- a/src/IO/tls.c +++ b/src/IO/tls.c @@ -932,6 +932,46 @@ static void Tls_close_by_key(int connkey) } } +static void Tls_print_cert_chain(SSL *ssl) +{ + STACK_OF(X509) *sk = SSL_get_peer_cert_chain(ssl); + + if (sk) { + const uint_t buflen = 4096; + char buf[buflen]; + int i, n = sk_X509_num(sk); + X509 *cert; + EVP_PKEY *public_key; + int key_type, key_bits; + const char *type_str; + + for (i = 0; i < n; i++) { + cert = sk_X509_value(sk, i); + public_key = X509_get_pubkey(cert); + key_type = EVP_PKEY_type(public_key->type); + type_str = key_type == EVP_PKEY_RSA ? "RSA" : + key_type == EVP_PKEY_DSA ? "DSA" : + key_type == EVP_PKEY_DH ? "DH" : + key_type == EVP_PKEY_EC ? "EC" : "???"; + key_bits = EVP_PKEY_bits(public_key); + X509_NAME_oneline(X509_get_subject_name(cert), buf, buflen); + buf[buflen-1] = '\0'; + MSG("%d-bit %s: %s\n", key_bits, type_str, buf); + EVP_PKEY_free(public_key); + + if (key_type == EVP_PKEY_RSA && key_bits <= 1024) { + /* TODO: Gather warnings into one popup. */ + MSG_WARN("In 2014/5, browsers have been deprecating 1024-bit RSA " + "keys.\n"); + } + } + + X509_NAME_oneline(X509_get_issuer_name(cert), buf, buflen); + buf[buflen-1] = '\0'; + MSG("root: %s\n", buf); + } +} + /* * Connect, set a callback if it's still not completed. If completed, check * the certificate and report back to http. @@ -999,12 +1039,14 @@ static void Tls_connect(int fd, int connkey) Server_t *srv = dList_find_custom(servers, conn->url, Tls_servers_cmp); if (srv->cert_status == CERT_STATUS_RECEIVING) { - /* Making first connection with the server */ - const char *version = SSL_get_version(conn->ssl); - const SSL_CIPHER *cipher = SSL_get_current_cipher(conn->ssl); + /* Making first connection with the server. Show some information. */ + SSL *ssl = conn->ssl; + const char *version = SSL_get_version(ssl); + const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl); MSG("%s: %s, cipher %s\n", URL_AUTHORITY(conn->url), version, SSL_CIPHER_get_name(cipher)); + Tls_print_cert_chain(ssl); } if (srv->cert_status == CERT_STATUS_USER_ACCEPTED || -- cgit v1.2.3 From 7bc101f0e1c65b688715bec4d1917a358ebd0d75 Mon Sep 17 00:00:00 2001 From: corvid Date: Sat, 30 May 2015 22:37:38 +0000 Subject: fix warning --- src/IO/tls.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/IO/tls.c b/src/IO/tls.c index 9368d563..6355f4f5 100644 --- a/src/IO/tls.c +++ b/src/IO/tls.c @@ -940,7 +940,7 @@ static void Tls_print_cert_chain(SSL *ssl) const uint_t buflen = 4096; char buf[buflen]; int i, n = sk_X509_num(sk); - X509 *cert; + X509 *cert = NULL; EVP_PKEY *public_key; int key_type, key_bits; const char *type_str; @@ -966,9 +966,11 @@ static void Tls_print_cert_chain(SSL *ssl) } } - X509_NAME_oneline(X509_get_issuer_name(cert), buf, buflen); - buf[buflen-1] = '\0'; - MSG("root: %s\n", buf); + if (cert) { + X509_NAME_oneline(X509_get_issuer_name(cert), buf, buflen); + buf[buflen-1] = '\0'; + MSG("root: %s\n", buf); + } } } -- cgit v1.2.3 From b7fbeaab0b05dd06440a83f1bf2575ff0fa628fc Mon Sep 17 00:00:00 2001 From: corvid Date: Sun, 31 May 2015 01:11:03 +0000 Subject: fix up socket queue --- src/IO/http.c | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/IO/http.c b/src/IO/http.c index 7deab2e4..5aca1703 100644 --- a/src/IO/http.c +++ b/src/IO/http.c @@ -76,6 +76,7 @@ typedef struct { bool_t https; int active_conns; + int running_the_queue; Dlist *queue; } Server_t; @@ -244,13 +245,19 @@ static void Http_connect_queued_sockets(Server_t *srv) SocketData_t *sd; int i; + srv->running_the_queue++; + for (i = 0; (i < dList_length(srv->queue) && srv->active_conns < prefs.http_max_conns); i++) { sd = dList_nth_data(srv->queue, i); - if (!(sd->flags & HTTP_SOCKET_TO_BE_FREED)) { + if (sd->flags & HTTP_SOCKET_TO_BE_FREED) { + dList_remove(srv->queue, sd); + dFree(sd); + i--; + } else { int connect_ready = TLS_CONNECT_READY; if (sd->flags & HTTP_SOCKET_TLS) @@ -266,15 +273,15 @@ static void Http_connect_queued_sockets(Server_t *srv) Http_connect_socket(sd->Info); } } - if (sd->flags & HTTP_SOCKET_TO_BE_FREED) { - dList_remove(srv->queue, sd); - dFree(sd); - i--; - } } _MSG("Queue http%s://%s:%u len %d\n", srv->https ? "s" : "", srv->host, srv->port, dList_length(srv->queue)); + + if (--srv->running_the_queue == 0) { + if (srv->active_conns == 0) + Http_server_remove(srv); + } } /* @@ -303,8 +310,6 @@ static void Http_socket_free(int SKey) (S->flags & HTTP_SOCKET_TLS)); srv->active_conns--; Http_connect_queued_sockets(srv); - if (srv->active_conns == 0) - Http_server_remove(srv); } a_Url_free(S->url); dFree(S); @@ -980,6 +985,7 @@ static Server_t *Http_server_get(const char *host, uint_t port, bool_t https) srv = dNew0(Server_t, 1); srv->queue = dList_new(10); + srv->running_the_queue = 0; srv->host = dStrdup(host); srv->port = port; srv->https = https; @@ -990,11 +996,16 @@ static Server_t *Http_server_get(const char *host, uint_t port, bool_t https) static void Http_server_remove(Server_t *srv) { - assert(dList_length(srv->queue) == 0); - dList_free(srv->queue); - dList_remove_fast(servers, srv); - dFree(srv->host); - dFree(srv); + SocketData_t *sd; + + while ((sd = dList_nth_data(srv->queue, 0))) { + dList_remove_fast(srv->queue, sd); + dFree(sd); + } + dList_free(srv->queue); + dList_remove_fast(servers, srv); + dFree(srv->host); + dFree(srv); } static void Http_servers_remove_all() -- cgit v1.2.3 From f8c3e19ac18075a45c48a5fc36cd2499defaf7e1 Mon Sep 17 00:00:00 2001 From: corvid Date: Sun, 31 May 2015 02:00:49 +0000 Subject: rm MSG --- src/cache.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/cache.c b/src/cache.c index 2cc8c0aa..9ff1cf14 100644 --- a/src/cache.c +++ b/src/cache.c @@ -857,17 +857,6 @@ static void Cache_finish_msg(CacheEntry_t *entry) MSG("Expected size: %d, Transfer size: %d\n", entry->ExpectedSize, entry->TransferSize); } - if (!entry->TransferSize && !(entry->Flags & CA_Redirect) && - (entry->Flags & WEB_RootUrl)) { - char *eol = strchr(entry->Header->str, '\n'); - if (eol) { - char *status_line = dStrndup(entry->Header->str, - eol - entry->Header->str); - MSG_HTTP("Body of %s was empty. Server sent status: %s\n", - URL_STR_(entry->Url), status_line); - dFree(status_line); - } - } entry->Flags |= CA_GotData; entry->Flags &= ~CA_Stopped; /* it may catch up! */ if (entry->TransferDecoder) { -- cgit v1.2.3 From d654825f7fecb9c081b5a12fb7d288ca6dddbfd1 Mon Sep 17 00:00:00 2001 From: corvid Date: Sun, 31 May 2015 02:17:35 +0000 Subject: rm MSG --- src/cache.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/cache.c b/src/cache.c index 9ff1cf14..d8f1a123 100644 --- a/src/cache.c +++ b/src/cache.c @@ -756,6 +756,7 @@ static void Cache_parse_header(CacheEntry_t *entry) if (!web->requester || a_Url_same_organization(entry->Url, web->requester)) { + /* If cookies are third party, don't even consider them. */ char *server_date = Cache_parse_field(header, "Date"); a_Cookies_set(Cookies, entry->Url, server_date); @@ -764,10 +765,6 @@ static void Cache_parse_header(CacheEntry_t *entry) } } } - if (i >= dList_length(ClientQueue)) { - MSG("Cache: cookies not accepted from '%s'\n", URL_STR(entry->Url)); - } - for (i = 0; (data = dList_nth_data(Cookies, i)); ++i) dFree(data); dList_free(Cookies); -- cgit v1.2.3 From 442cb2c37f7c6a218b2bc87fe18381e96d4bfe5e Mon Sep 17 00:00:00 2001 From: corvid Date: Mon, 1 Jun 2015 03:26:40 +0000 Subject: url: rm dead code --- src/url.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/url.c b/src/url.c index aa211fb7..7112f461 100644 --- a/src/url.c +++ b/src/url.c @@ -213,7 +213,6 @@ void a_Url_free(DilloUrl *url) * Resolve the URL as RFC3986 suggests. */ static Dstr *Url_resolve_relative(const char *RelStr, - DilloUrl *BaseUrlPar, const char *BaseStr) { char *p, *s, *e; @@ -224,9 +223,7 @@ static Dstr *Url_resolve_relative(const char *RelStr, /* parse relative URL */ RelUrl = Url_object_new(RelStr); - if (BaseUrlPar) { - BaseUrl = BaseUrlPar; - } else if (RelUrl->scheme == NULL) { + if (RelUrl->scheme == NULL) { /* only required when there's no in RelStr */ BaseUrl = Url_object_new(BaseStr); } @@ -336,8 +333,7 @@ static Dstr *Url_resolve_relative(const char *RelStr, done: dStr_free(Path, TRUE); a_Url_free(RelUrl); - if (BaseUrl != BaseUrlPar) - a_Url_free(BaseUrl); + a_Url_free(BaseUrl); return SolvedUrl; } @@ -406,7 +402,7 @@ DilloUrl* a_Url_new(const char *url_str, const char *base_url) } /* Resolve the URL */ - SolvedUrl = Url_resolve_relative(urlstr, NULL, base_url); + SolvedUrl = Url_resolve_relative(urlstr, base_url); _MSG("SolvedUrl = %s\n", SolvedUrl->str); /* Fill url data */ -- cgit v1.2.3 From b4b85a7947a05841aa728a98d42c1e3ab66489d6 Mon Sep 17 00:00:00 2001 From: corvid Date: Mon, 1 Jun 2015 15:25:52 +0000 Subject: TLS servers sorted --- src/IO/tls.c | 69 ++++++++++++++++++++++++++++++++---------------------------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/src/IO/tls.c b/src/IO/tls.c index 6355f4f5..be8b7b92 100644 --- a/src/IO/tls.c +++ b/src/IO/tls.c @@ -341,16 +341,30 @@ static int Tls_save_certificate_home(X509 * cert) } /* - * Test whether a URL corresponds to a server. + * Ordered comparison of servers. */ static int Tls_servers_cmp(const void *v1, const void *v2) { - Server_t *s = (Server_t *)v1; + const Server_t *s1 = (const Server_t *)v1, *s2 = (const Server_t *)v2; + int cmp = dStrAsciiCasecmp(s1->hostname, s2->hostname); + + if (!cmp) + cmp = s1->port - s2->port; + return cmp; +} +/* + * Ordered comparison of server with URL. + */ +static int Tls_servers_by_url_cmp(const void *v1, const void *v2) +{ + const Server_t *s = (const Server_t *)v1; const DilloUrl *url = (const DilloUrl *)v2; - const char *host = URL_HOST(url); - int port = URL_PORT(url); - return (dStrAsciiCasecmp(s->hostname, host) || (port != s->port)); + int cmp = dStrAsciiCasecmp(s->hostname, URL_HOST(url)); + + if (!cmp) + cmp = s->port - URL_PORT(url); + return cmp; } /* @@ -358,41 +372,31 @@ static int Tls_servers_cmp(const void *v1, const void *v2) * Once we have the certificate, know whether we like it -- and whether the * user accepts it -- HTTP can run through queued sockets as normal. * - * Return: 1 means yes, 0 means not yet, -1 means never. - * TODO: Something clearer or different. + * Return: TLS_CONNECT_READY or TLS_CONNECT_NOT_YET or TLS_CONNECT_NEVER. */ int a_Tls_connect_ready(const DilloUrl *url) { Server_t *s; - int i, len; - const char *host = URL_HOST(url); - const int port = URL_PORT(url); int ret = TLS_CONNECT_READY; dReturn_val_if_fail(ssl_context, TLS_CONNECT_NEVER); - len = dList_length(servers); - - for (i = 0; i < len; i++) { - s = dList_nth_data(servers, i); + if ((s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp))) { + if (s->cert_status == CERT_STATUS_RECEIVING) + ret = TLS_CONNECT_NOT_YET; + else if (s->cert_status == CERT_STATUS_BAD) + ret = TLS_CONNECT_NEVER; - if (!dStrAsciiCasecmp(s->hostname, host) && (port == s->port)) { - if (s->cert_status == CERT_STATUS_RECEIVING) - ret = TLS_CONNECT_NOT_YET; - else if (s->cert_status == CERT_STATUS_BAD) - ret = TLS_CONNECT_NEVER; + if (s->cert_status == CERT_STATUS_NONE) + s->cert_status = CERT_STATUS_RECEIVING; + } else { + s = dNew(Server_t, 1); - if (s->cert_status == CERT_STATUS_NONE) - s->cert_status = CERT_STATUS_RECEIVING; - return ret; - } + s->hostname = dStrdup(URL_HOST(url)); + s->port = URL_PORT(url); + s->cert_status = CERT_STATUS_RECEIVING; + dList_insert_sorted(servers, s, Tls_servers_cmp); } - s = dNew(Server_t, 1); - - s->port = port; - s->hostname = dStrdup(host); - s->cert_status = CERT_STATUS_RECEIVING; - dList_append(servers, s); return ret; } @@ -402,7 +406,7 @@ int a_Tls_connect_ready(const DilloUrl *url) */ static int Tls_user_said_no(const DilloUrl *url) { - Server_t *s = dList_find_custom(servers, url, Tls_servers_cmp); + Server_t *s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp); if (!s) return FALSE; @@ -902,7 +906,7 @@ static int Tls_examine_certificate(SSL *ssl, Server_t *srv,const char *host) void a_Tls_reset_server_state(const DilloUrl *url) { if (servers) { - Server_t *s = dList_find_custom(servers, url, Tls_servers_cmp); + Server_t *s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp); if (s && s->cert_status == CERT_STATUS_RECEIVING) s->cert_status = CERT_STATUS_NONE; @@ -1038,7 +1042,8 @@ static void Tls_connect(int fd, int connkey) MSG("SSL_get_error() returned %d on a connect.\n", err1_ret); } } else { - Server_t *srv = dList_find_custom(servers, conn->url, Tls_servers_cmp); + Server_t *srv = dList_find_sorted(servers, conn->url, + Tls_servers_by_url_cmp); if (srv->cert_status == CERT_STATUS_RECEIVING) { /* Making first connection with the server. Show some information. */ -- cgit v1.2.3 From 40989a38306d1dd5d356a6b049574e6434e1abb7 Mon Sep 17 00:00:00 2001 From: corvid Date: Mon, 1 Jun 2015 16:40:10 +0000 Subject: rm the old-style url alt stuff --- src/html.cc | 2 -- src/url.c | 14 -------------- src/url.h | 3 --- 3 files changed, 19 deletions(-) diff --git a/src/html.cc b/src/html.cc index d6b64a19..fccf6bfb 100644 --- a/src/html.cc +++ b/src/html.cc @@ -2514,8 +2514,6 @@ static void if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "href"))) { url = a_Html_url_new(html, attrbuf, NULL, 0); dReturn_if_fail ( url != NULL ); - if ((attrbuf = a_Html_get_attr(html, tag, tagsize, "alt"))) - a_Url_set_alt(url, attrbuf); link = Html_set_new_link(html, &url); } diff --git a/src/url.c b/src/url.c index 7112f461..6fee97c3 100644 --- a/src/url.c +++ b/src/url.c @@ -204,7 +204,6 @@ void a_Url_free(DilloUrl *url) dFree((char *)url->hostname); dFree((char *)url->buffer); dStr_free(url->data, 1); - dFree((char *)url->alt); dFree(url); } } @@ -352,7 +351,6 @@ done: * port = 8080 * flags = URL_Get * data = Dstr * ("") - * alt = NULL * ismap_url_len = 0 * } * @@ -431,7 +429,6 @@ DilloUrl* a_Url_dup(const DilloUrl *ori) url->url_string = dStr_new(URL_STR(ori)); url->port = ori->port; url->flags = ori->flags; - url->alt = dStrdup(ori->alt); url->ismap_url_len = ori->ismap_url_len; url->illegal_chars = ori->illegal_chars; url->illegal_chars_spc = ori->illegal_chars_spc; @@ -490,17 +487,6 @@ void a_Url_set_data(DilloUrl *u, Dstr **data) } } -/* - * Set DilloUrl alt (alternate text to the URL. Used by image maps) - */ -void a_Url_set_alt(DilloUrl *u, const char *alt) -{ - if (u) { - dFree((char *)u->alt); - u->alt = dStrdup(alt); - } -} - /* * Set DilloUrl ismap coordinates * (this is optimized for not hogging the CPU) diff --git a/src/url.h b/src/url.h index 6920f769..a280fd84 100644 --- a/src/url.h +++ b/src/url.h @@ -47,7 +47,6 @@ #define URL_QUERY_(u) (u)->query #define URL_FRAGMENT_(u) (u)->fragment #define URL_HOST_(u) a_Url_hostname(u) -#define URL_ALT_(u) (u)->alt #define URL_STR_(u) a_Url_str(u) /* this returns a Dstr* */ #define URL_DATA_(u) (u)->data @@ -94,7 +93,6 @@ typedef struct { int port; int flags; Dstr *data; /* POST */ - const char *alt; /* "alt" text (used by image maps) */ int ismap_url_len; /* Used by server side image maps */ int illegal_chars; /* number of illegal chars */ int illegal_chars_spc; /* number of illegal space chars */ @@ -109,7 +107,6 @@ DilloUrl* a_Url_dup(const DilloUrl *u); int a_Url_cmp(const DilloUrl *A, const DilloUrl *B); void a_Url_set_flags(DilloUrl *u, int flags); void a_Url_set_data(DilloUrl *u, Dstr **data); -void a_Url_set_alt(DilloUrl *u, const char *alt); void a_Url_set_ismap_coords(DilloUrl *u, char *coord_str); char *a_Url_decode_hex_str(const char *str); char *a_Url_encode_hex_str(const char *str); -- cgit v1.2.3 From 67e71ab81f83ecd09e97c5e29cb240431450a7ec Mon Sep 17 00:00:00 2001 From: corvid Date: Mon, 1 Jun 2015 16:55:55 +0000 Subject: url: rm unused flags --- src/url.c | 1 - src/url.h | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/url.c b/src/url.c index 6fee97c3..e2eac48a 100644 --- a/src/url.c +++ b/src/url.c @@ -498,7 +498,6 @@ void a_Url_set_ismap_coords(DilloUrl *u, char *coord_str) if (!u->ismap_url_len) { /* Save base-url length (without coords) */ u->ismap_url_len = URL_STR_(u) ? u->url_string->len : 0; - a_Url_set_flags(u, URL_FLAGS(u) | URL_Ismap); } if (u->url_string) { dStr_truncate(u->url_string, u->ismap_url_len); diff --git a/src/url.h b/src/url.h index a280fd84..93d198f8 100644 --- a/src/url.h +++ b/src/url.h @@ -22,12 +22,8 @@ */ #define URL_Get (1 << 0) #define URL_Post (1 << 1) -#define URL_ISindex (1 << 2) -#define URL_Ismap (1 << 3) -#define URL_RealmAccess (1 << 4) #define URL_E2EQuery (1 << 5) -#define URL_ReloadImages (1 << 6) #define URL_ReloadPage (1 << 7) #define URL_ReloadFromCache (1 << 8) @@ -68,9 +64,8 @@ #define URL_QUERY(u) NPTR2STR(URL_QUERY_(u)) #define URL_FRAGMENT(u) NPTR2STR(URL_FRAGMENT_(u)) #define URL_HOST(u) NPTR2STR(URL_HOST_(u)) -#define URL_DATA(u) URL_DATA_(u) -#define URL_ALT(u) NPTR2STR(URL_ALT_(u)) #define URL_STR(u) NPTR2STR(URL_STR_(u)) +#define URL_DATA(u) URL_DATA_(u) #define URL_PORT(u) URL_PORT_(u) #define URL_FLAGS(u) URL_FLAGS_(u) #define URL_ILLEGAL_CHARS(u) URL_ILLEGAL_CHARS_(u) -- cgit v1.2.3 From 3aa9e64c49310a0e47e63a1ccded6b9cd223519f Mon Sep 17 00:00:00 2001 From: corvid Date: Mon, 1 Jun 2015 17:15:12 +0000 Subject: const --- src/IO/tls.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/IO/tls.c b/src/IO/tls.c index be8b7b92..4ae40961 100644 --- a/src/IO/tls.c +++ b/src/IO/tls.c @@ -210,7 +210,7 @@ static void Tls_load_certificates() */ uint_t u; char *userpath; - static const char *ca_files[] = { + static const char *const ca_files[] = { "/etc/ssl/certs/ca-certificates.crt", "/etc/pki/tls/certs/ca-bundle.crt", "/usr/share/ssl/certs/ca-bundle.crt", @@ -219,7 +219,7 @@ static void Tls_load_certificates() CA_CERTS_FILE }; - static const char *ca_paths[] = { + static const char *const ca_paths[] = { "/etc/ssl/certs/", CA_CERTS_DIR }; -- cgit v1.2.3 From eb7ee4703ced8a02404eb0ebfa5b771fc5e916d5 Mon Sep 17 00:00:00 2001 From: Sebastian Geerken Date: Mon, 1 Jun 2015 21:36:41 +0200 Subject: Updated doc directory. --- doc/Images.txt | 3 + doc/Imgbuf.txt | 177 ------------------------------------------------------ doc/Makefile.am | 6 +- doc/Selection.txt | 149 --------------------------------------------- 4 files changed, 5 insertions(+), 330 deletions(-) delete mode 100644 doc/Imgbuf.txt delete mode 100644 doc/Selection.txt diff --git a/doc/Images.txt b/doc/Images.txt index 6a36e6f5..62082e48 100644 --- a/doc/Images.txt +++ b/doc/Images.txt @@ -1,5 +1,8 @@ January 2009, --Jcid +Update June 2015: See also doc/dw-images-and-backgrounds.doc, or +../html/dw-images-and-backgrounds.html (generated by doxygen). + ------ IMAGES ------ diff --git a/doc/Imgbuf.txt b/doc/Imgbuf.txt deleted file mode 100644 index f4a56660..00000000 --- a/doc/Imgbuf.txt +++ /dev/null @@ -1,177 +0,0 @@ -Aug 2004, S.Geerken@ping.de - -============= -Image Buffers -============= - -General -======= - -Image buffers depend on the platform (see DwRender.txt), but have a -general, platform independant interface, which is described in this -section. The next section describes the Gdk version of Imgbuf. - -The structure ImgBuf will become part of the image processing, between -image data decoding and the widget DwImage. Its purposes are - - 1. storing the image data, - 2. handling scaled versions of this buffer, and - 3. drawing. - -The latter must be done independently from the window. - -Storing Image Data ------------------- -Imgbuf supports five image types, which are listed in the table -below. The representation defines, how the colors are stored within -the data, which is passed to a_Imgbuf_copy_row(). - - | bytes per | - type | pixel | representation - ---------------+-----------+------------------------- - RGB | 3 | red, green, blue - RGBA | 4 | red, green, blue, alpha - gray | 1 | gray value - indexed | 1 | index to colormap - indexed alpha | 1 | index to colormap - -The last two types need a colormap, which is set by -a_Imgbuf_set_cmap(), which must be called before -a_Imgbuf_copy_row(). This function expects the colors as 32 bit -unsigned integers, which have the format 0xrrbbgg (for indexed -images), or 0xaarrggbb (for indexed alpha), respectively. - -Scaling -------- -The buffer with the original size, which was created by -a_Imgbuf_new(), is called root buffer. Imgbuf provides the ability to -scale buffers. Generally, both root buffers, as well as scaled -buffers, may be shared, memory management is done by reference -counters. - -Via a_Imgbuf_get_scaled_buf(), you can retrieve a scaled buffer. The -way, how this function works in detail, is described in the code, but -generally, something like this works always, in an efficient way: - - old_buf = cur_buf; - cur_buf = a_Imgbuf_get_scaled_buf(old_buf, with, height); - a_Imgbuf_unref (old_buf); - -Old_buf may both be a root buffer, or a scaled buffer. - -(As an exception, there should always be a reference on the root -buffer, since scaled buffers cannot exist without the root buffer, but -on the other side, do not hold references on it. So, if in the example -above, old_buf would be a root buffer, and there would, at the -beginning, only be one reference on it, new_buf would also be -destroyed, along with old_buf. Therefore, an external reference must -be added to the root buffer, which is in dillo done within the dicache -module.) - -The root buffer keeps a list of all children, and all operations -operating on the image data (a_Imgbuf_copy_row() and -a_Imgbuf_set_cmap()) are delegated to the scaled buffers, when -processed, and inherited, when a new scaled buffer is created. This -means, that they must only be performed for the root buffer. - -Drawing -------- -There are two situations, when drawing is necessary: - - 1. To react on expose events, the function a_Imgbuf_draw() can be - used. Notice that the exact signature of this function is - platform dependant. - - 2. When a row has been copied, it has to be drawn. To determine the - area, which has to be drawn, the function - a_Imgbuf_get_row_area() should be used. In dillo, the dicache - module will first call a_Img_copy_row(), and then call - a_Dw_image_draw_row() for the images connected to this image - buffer. a_Dw_image_draw_row() will then call - p_Dw_widget_queue_draw(), with an area determined by - a_Imgbuf_get_row_area(). - - -The Gdk Implementation -====================== - -The Gdk implementation is used by the Gtk+ platform. [... todo] - - -Global Scalers -============== - -In some cases, there is a context, where images have to be scaled -often, by a relatively constant factor. For example, the preview -window (GtkDwPreview) draws images via the Imgbuf draw functions, but -uses scaled buffers. Scaling such a buffer each time it is needed, -causes huge performance losses. On the other hand, if the preview -window would keep these scaled buffers (e.g. by lazy mapping of the -original buffer to the scaled buffer), the scaled buffers get never -freed, since the view is not told about, when the original buffer is -not needed anymore. (n.b., that currently, the scaled buffers are -destroyed, when the original buffer is destroyed, but this may change, -and even this would leave "zombies" in this mapping structure, where -the values refer to dead pointers). - -It is sufficient, that references on the scaled buffers are referred -somehow, so that they do not get destroyed between different -usages. The caller (in this case the preview) simply requests a scaled -buffer, but the Imgbuf returns this from the list of already scaled -buffers. - -These references are hold by special structures, which are called -"scalers". There are two types of scalers, local scalers, which are -bound to image buffers, and global scalers, which refer to multiple -scalers. - -What happens in different situations: - - - The caller (e.g. the preview) requests a scaled buffer. For this, - it uses a special method, which also passes the global image - scaler, which was created before (for the preview, there is a 1-1 - association). The Imgbuf uses this global image scaler, to - identify the caller, and keeps a list of them. If this global - scaler is not yet in the list, it is added, and a local scaler is - created. - - - - - - -There are three images in the page, i1a, i1b, and i2. I1a and i1b -refer to the same image recource, represented by the root image buffer -iba, which original size is 200 x 200. I1a is displayed in original -size, while i1b is displayed at 100 x 100. I2 refers to an other -recource, ibb, which has the size 300 x 300. I2 is shown in original -size. - - - :DwRenderLayout ------------------- :DwPage ----------. - / \ | - ,----' `----. ,------ i1a:DwImage --+ - / \ | | - view1:GtkDwViewport view2:GtkDwPreview | ,---- i1b:DwImage --| - | | | | - ,------------------------------' | | ,-- i2: DwImage --' - | | | | - | ,-------------------------------------' | | - | | ,--------------------------------' | - | | | ,----' - | | | | - | V | V - | iba:Imgbuf | ibb:Imgbuf -- 30x30 - | | | V | ^ - | | +- 100x100 ,- 20x20 ,- 10x10 | | - | | | | ^ | ^ | | - | | `----------+----|---' | `--. ,--' - | | ,--------------' | | | - | | | ,------------------' | | - | | | | | | - | lca:ImgbufLSc lcb:ImgbufLSc - | (factor 1/10) (factor 1/10) - | \ / - | `-----------. ,-------------------' - | \ / - `------------------> scl:ImgbufGSc - (factor 1/10) diff --git a/doc/Makefile.am b/doc/Makefile.am index 8ade3d15..71c7c32d 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -11,8 +11,8 @@ EXTRA_DIST = \ dw-widget-sizes.doc \ dw-changes.doc \ dw-images-and-backgrounds.doc \ - dw-dw-out-of-flow.doc \ - dw-dw-out-of-flow-2.doc \ + dw-out-of-flow.doc \ + dw-out-of-flow-2.doc \ fltk-problems.doc \ rounding-errors.doc \ uml-legend.doc \ @@ -38,9 +38,7 @@ EXTRA_DIST = \ HtmlParser.txt \ IO.txt \ Images.txt \ - Imgbuf.txt \ NC_design.txt \ - Selection.txt \ Dpid.txt \ CCCwork.txt \ README \ diff --git a/doc/Selection.txt b/doc/Selection.txt deleted file mode 100644 index 7904bd94..00000000 --- a/doc/Selection.txt +++ /dev/null @@ -1,149 +0,0 @@ -Apr 2003, S.Geerken@ping.de -Last update: Dec 2004 - -========= -Selection -========= - -The selection module (selection.[ch]) handles selections, as well as -activation of links, which is closely related. - - -General Overview -================ - -The selection module defines a structure "Selection", which is -associated to GtkDwViewport, and so to a widget tree. The selection -state is controlled by "abstract events", which are sent by single -widgets by calling one of the following functions: - - a_Selection_button_press for button press events, - a_Selection_button_release for button release events, and - a_Selection_button_motion for motion events (with pressed mouse - button). - -The widget must construct simple iterators (DwIterator), which will be -transferred to extended iterators (DwExtIterator), see below for more -details. All event handling functions have the same signature, the -arguments in detail are: - - - DwIterator *it the iterator pointing on the item under - the mouse pointer, - - gint char_pos the exact (character) position within - the iterator, - - gint link if this item is associated with a link, - its number (see DwImage, section - "signals" for the meaning), otherwise - -1, - - GdkEventButton *event the event itself; only the button is - used, - - gboolean within_content TRUE, if there is some selectable - content unter the mouse cursor; if set - to FALSE, the "full screen" feature is - used on double click. - -In some cases, char_pos would be difficult to determine. E.g., when -the DwPage widget decides that the user is pointing on a position -_at_the_end_ of an image (DwImage), it constructs a simple iterator -pointing on this image widget. In a simple iterator, that fact that -the pointer is at the end, would be represented by char_pos == 1. But -when transferring this simple iterator into an extended iterator, this -simple iterator is discarded and instead the stack has an iterator -pointing to text at the top. As a result, only the first letter of the -ALT text would be copied. - -To avoid this problem, widgets should in this case pass SELECTION_EOW -(end of word) as char_pos, which is then automatically reduced to the -actual length of the extended(!) iterator. - -The return value is the same as in DwWidget event handling methods. -I.e., in most cases, they should simply return it. The events -"link_pressed", "link_released" and "link_clicked" (but not -"link_entered") are emitted by these functions, so that widgets which -let the selection module handle links, should only emit "link_entered" -for themselves. (See DwImage.txt for a description of this.) - - -Selection State -=============== - -Selection interferes with handling the activation of links, so the -latter is also handled by the selection module. Details are based on -following guidelines: - - 1. It should be simple to select links and to start selection in - links. The rule to distinguish between link activation and - selection is that the selection starts as soon as the user leaves - the link. (This is, IMO, a useful feature. Even after drag and - drop has been implemented in dillo, this should be somehow - preserved.) - - 2. The selection should stay as long as possible, i.e., the old - selection is only cleared when a new selection is started. - -The latter leads to a model with two states: the selection state and -the link handling state. - -The general selection works, for events not pointing on links, like -this (numbers in parantheses after the event denote the button, "n" -means arbitrary button): - - motion(1) - ,-----. - | | - press(1) on non-link V | - NONE -----------------------> SELECTING <----------------. - ^ | | - | | release(1) | - | | | press(1) - | no V yes | - `----------------------- Anything selected? --------> SELECTED - -The selected region is represented by two DwExtIterators. - -Links are handled by a different state machine: - - ,-----------------------------. - | | - | Switch to selection - | (SELECTING) for n == 1. - | ^ - | | no - | | yes - | Still the same link? --. - | ^ | - | | | - | | motion(n) | - V press(n) on links | | - NONE ---------------------> PRESSED(n) <-----' - ^ | - | | release(n) - | | - | V yes - | Still the same link? -----------------. - | | | - | | no V - | V Send "clicked" signal. - | Switch to selection | - | (SELECTED) for n == 1. | - | | | - |`----------------------------' | - | | - `----------------------------------------------------------' - -Switching to selection simply means that the selection state will -eventually be SELECTED/SELECTING, with the original and the actual -position making up the selection region. This happens for button 1, -events with buttons other than 1 do not affect selection at all. - - -TODO -==== - -* a_Selection_button_motion currently always assumes that button 1 has - been pressed (since otherwise it would not do anything). This should - be made a bit cleaner. - -* The selection should be cleared, when the user selects something - somewhere else (perhaps switched into "non-active" mode, as some - Gtk+ widgets do). -- cgit v1.2.3 From 1463c3936ce6a57352590b901c9dbd6bc2f2086d Mon Sep 17 00:00:00 2001 From: Sebastian Geerken Date: Mon, 1 Jun 2015 22:00:10 +0200 Subject: Split up user and developer documentation. --- Doxyfile | 4 +- devdoc/CCCwork.txt | 153 +++++++++ devdoc/Cache.txt | 166 +++++++++ devdoc/Dillo.txt | 96 ++++++ devdoc/Dpid.txt | 331 ++++++++++++++++++ devdoc/HtmlParser.txt | 124 +++++++ devdoc/IO.txt | 468 +++++++++++++++++++++++++ devdoc/Images.txt | 129 +++++++ devdoc/NC_design.txt | 127 +++++++ devdoc/README | 51 +++ devdoc/dw-changes.doc | 105 ++++++ devdoc/dw-example-screenshot.png | Bin 0 -> 2264 bytes devdoc/dw-floats-01.png | Bin 0 -> 3410 bytes devdoc/dw-grows.doc | 202 +++++++++++ devdoc/dw-images-and-backgrounds.doc | 235 +++++++++++++ devdoc/dw-layout-views.doc | 256 ++++++++++++++ devdoc/dw-layout-widgets.doc | 267 +++++++++++++++ devdoc/dw-line-breaking.doc | 470 ++++++++++++++++++++++++++ devdoc/dw-map.doc | 59 ++++ devdoc/dw-out-of-flow-2.doc | 69 ++++ devdoc/dw-out-of-flow-floats.doc | 121 +++++++ devdoc/dw-out-of-flow.doc | 214 ++++++++++++ devdoc/dw-overview.doc | 158 +++++++++ devdoc/dw-size-of-widget.png | Bin 0 -> 1749 bytes devdoc/dw-style-box-model.png | Bin 0 -> 3889 bytes devdoc/dw-style-length-absolute.png | Bin 0 -> 575 bytes devdoc/dw-style-length-percentage.png | Bin 0 -> 890 bytes devdoc/dw-style-length-relative.png | Bin 0 -> 868 bytes devdoc/dw-textblock-collapsing-spaces-1-1.png | Bin 0 -> 641 bytes devdoc/dw-textblock-collapsing-spaces-1-2.png | Bin 0 -> 521 bytes devdoc/dw-textblock-collapsing-spaces-2-1.png | Bin 0 -> 802 bytes devdoc/dw-textblock-collapsing-spaces-2-2.png | Bin 0 -> 586 bytes devdoc/dw-usage.doc | 375 ++++++++++++++++++++ devdoc/dw-viewport-with-scrollbar.png | Bin 0 -> 755 bytes devdoc/dw-viewport-without-scrollbar.png | Bin 0 -> 542 bytes devdoc/dw-widget-sizes.doc | 277 +++++++++++++++ devdoc/fltk-problems.doc | 180 ++++++++++ devdoc/index.doc | 48 +++ devdoc/lout.doc | 95 ++++++ devdoc/not-so-simple-container.png | Bin 0 -> 5738 bytes devdoc/rounding-errors.doc | 35 ++ devdoc/uml-legend.doc | 195 +++++++++++ doc/CCCwork.txt | 153 --------- doc/Cache.txt | 166 --------- doc/Dillo.txt | 96 ------ doc/Dpid.txt | 331 ------------------ doc/Dw.txt | 11 - doc/HtmlParser.txt | 124 ------- doc/IO.txt | 468 ------------------------- doc/Images.txt | 129 ------- doc/Makefile.am | 44 +-- doc/NC_design.txt | 127 ------- doc/README | 54 +-- doc/dw-changes.doc | 105 ------ doc/dw-example-screenshot.png | Bin 2264 -> 0 bytes doc/dw-floats-01.png | Bin 3410 -> 0 bytes doc/dw-grows.doc | 202 ----------- doc/dw-images-and-backgrounds.doc | 235 ------------- doc/dw-layout-views.doc | 256 -------------- doc/dw-layout-widgets.doc | 267 --------------- doc/dw-line-breaking.doc | 470 -------------------------- doc/dw-map.doc | 59 ---- doc/dw-out-of-flow-2.doc | 69 ---- doc/dw-out-of-flow-floats.doc | 121 ------- doc/dw-out-of-flow.doc | 214 ------------ doc/dw-overview.doc | 158 --------- doc/dw-size-of-widget.png | Bin 1749 -> 0 bytes doc/dw-style-box-model.png | Bin 3889 -> 0 bytes doc/dw-style-length-absolute.png | Bin 575 -> 0 bytes doc/dw-style-length-percentage.png | Bin 890 -> 0 bytes doc/dw-style-length-relative.png | Bin 868 -> 0 bytes doc/dw-textblock-collapsing-spaces-1-1.png | Bin 641 -> 0 bytes doc/dw-textblock-collapsing-spaces-1-2.png | Bin 521 -> 0 bytes doc/dw-textblock-collapsing-spaces-2-1.png | Bin 802 -> 0 bytes doc/dw-textblock-collapsing-spaces-2-2.png | Bin 586 -> 0 bytes doc/dw-usage.doc | 375 -------------------- doc/dw-viewport-with-scrollbar.png | Bin 755 -> 0 bytes doc/dw-viewport-without-scrollbar.png | Bin 542 -> 0 bytes doc/dw-widget-sizes.doc | 277 --------------- doc/fltk-problems.doc | 180 ---------- doc/index.doc | 48 --- doc/lout.doc | 95 ------ doc/not-so-simple-container.png | Bin 5738 -> 0 bytes doc/rounding-errors.doc | 35 -- doc/uml-legend.doc | 195 ----------- 85 files changed, 5015 insertions(+), 5059 deletions(-) create mode 100644 devdoc/CCCwork.txt create mode 100644 devdoc/Cache.txt create mode 100644 devdoc/Dillo.txt create mode 100644 devdoc/Dpid.txt create mode 100644 devdoc/HtmlParser.txt create mode 100644 devdoc/IO.txt create mode 100644 devdoc/Images.txt create mode 100644 devdoc/NC_design.txt create mode 100644 devdoc/README create mode 100644 devdoc/dw-changes.doc create mode 100644 devdoc/dw-example-screenshot.png create mode 100644 devdoc/dw-floats-01.png create mode 100644 devdoc/dw-grows.doc create mode 100644 devdoc/dw-images-and-backgrounds.doc create mode 100644 devdoc/dw-layout-views.doc create mode 100644 devdoc/dw-layout-widgets.doc create mode 100644 devdoc/dw-line-breaking.doc create mode 100644 devdoc/dw-map.doc create mode 100644 devdoc/dw-out-of-flow-2.doc create mode 100644 devdoc/dw-out-of-flow-floats.doc create mode 100644 devdoc/dw-out-of-flow.doc create mode 100644 devdoc/dw-overview.doc create mode 100644 devdoc/dw-size-of-widget.png create mode 100644 devdoc/dw-style-box-model.png create mode 100644 devdoc/dw-style-length-absolute.png create mode 100644 devdoc/dw-style-length-percentage.png create mode 100644 devdoc/dw-style-length-relative.png create mode 100644 devdoc/dw-textblock-collapsing-spaces-1-1.png create mode 100644 devdoc/dw-textblock-collapsing-spaces-1-2.png create mode 100644 devdoc/dw-textblock-collapsing-spaces-2-1.png create mode 100644 devdoc/dw-textblock-collapsing-spaces-2-2.png create mode 100644 devdoc/dw-usage.doc create mode 100644 devdoc/dw-viewport-with-scrollbar.png create mode 100644 devdoc/dw-viewport-without-scrollbar.png create mode 100644 devdoc/dw-widget-sizes.doc create mode 100644 devdoc/fltk-problems.doc create mode 100644 devdoc/index.doc create mode 100644 devdoc/lout.doc create mode 100644 devdoc/not-so-simple-container.png create mode 100644 devdoc/rounding-errors.doc create mode 100644 devdoc/uml-legend.doc delete mode 100644 doc/CCCwork.txt delete mode 100644 doc/Cache.txt delete mode 100644 doc/Dillo.txt delete mode 100644 doc/Dpid.txt delete mode 100644 doc/Dw.txt delete mode 100644 doc/HtmlParser.txt delete mode 100644 doc/IO.txt delete mode 100644 doc/Images.txt delete mode 100644 doc/NC_design.txt delete mode 100644 doc/dw-changes.doc delete mode 100644 doc/dw-example-screenshot.png delete mode 100644 doc/dw-floats-01.png delete mode 100644 doc/dw-grows.doc delete mode 100644 doc/dw-images-and-backgrounds.doc delete mode 100644 doc/dw-layout-views.doc delete mode 100644 doc/dw-layout-widgets.doc delete mode 100644 doc/dw-line-breaking.doc delete mode 100644 doc/dw-map.doc delete mode 100644 doc/dw-out-of-flow-2.doc delete mode 100644 doc/dw-out-of-flow-floats.doc delete mode 100644 doc/dw-out-of-flow.doc delete mode 100644 doc/dw-overview.doc delete mode 100644 doc/dw-size-of-widget.png delete mode 100644 doc/dw-style-box-model.png delete mode 100644 doc/dw-style-length-absolute.png delete mode 100644 doc/dw-style-length-percentage.png delete mode 100644 doc/dw-style-length-relative.png delete mode 100644 doc/dw-textblock-collapsing-spaces-1-1.png delete mode 100644 doc/dw-textblock-collapsing-spaces-1-2.png delete mode 100644 doc/dw-textblock-collapsing-spaces-2-1.png delete mode 100644 doc/dw-textblock-collapsing-spaces-2-2.png delete mode 100644 doc/dw-usage.doc delete mode 100644 doc/dw-viewport-with-scrollbar.png delete mode 100644 doc/dw-viewport-without-scrollbar.png delete mode 100644 doc/dw-widget-sizes.doc delete mode 100644 doc/fltk-problems.doc delete mode 100644 doc/index.doc delete mode 100644 doc/lout.doc delete mode 100644 doc/not-so-simple-container.png delete mode 100644 doc/rounding-errors.doc delete mode 100644 doc/uml-legend.doc diff --git a/Doxyfile b/Doxyfile index df509910..68ffcea9 100644 --- a/Doxyfile +++ b/Doxyfile @@ -852,7 +852,7 @@ EXAMPLE_RECURSIVE = NO # that contain images that are to be included in the documentation (see the # \image command). -IMAGE_PATH = doc +IMAGE_PATH = devdoc # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program @@ -1298,7 +1298,7 @@ QHP_NAMESPACE = # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_VIRTUAL_FOLDER = doc +QHP_VIRTUAL_FOLDER = devdoc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom diff --git a/devdoc/CCCwork.txt b/devdoc/CCCwork.txt new file mode 100644 index 00000000..1ea5d20e --- /dev/null +++ b/devdoc/CCCwork.txt @@ -0,0 +1,153 @@ +Last review: August 04, 2009 --jcid + + +---------------------------- +Internal working for the CCC +---------------------------- + + +HTTP protocol +------------- + + + Query: | + . + 1B --> 1B 1B --> 1B --> | -------------. + .----. .----. .----. . | +I |Capi| |http| | IO | | | + '----' '----' '----' . | + 1F <-- 1F 1F <-- 1F | V + . + | [Server] + Answer: . + + 2B --> 2B 2B --> 2B | | + .----. .----. .----. . | +II |Capi| |Dpi | | IO | | | + '----' '----' '----' . | + 2F <-- 2F 2F <-- 2F <-- | <------------' + . + | + +* a_Capi_open_url() builds both the Answer and Query chains at +once (Answer first then Query), to ensure a uniform structure +that avoids complexity (e.g. race conditions). + +* Http_get() sets a callback for the DNS hostname resolve. +Normally it comes later, but may also by issued immediately if +the hostname is cached. + +* The socket FD is passed by means of OpSend by the http module +once the remote IP is known and the socket is connected. + + + +Function calls for HTTP CCC +--------------------------- + + a_Capi_open_url + if (reload) + Capi OpStart 2B (answer) [Capi] --> [dpi] --> [IO] + Capi OpStart 1B (query) [Capi] --> [http] --> [IO] + Http_get + a_Cache_open_url + if URL_E2EReload -> prepare reload + if cached + client enqueue + delayed process queue + else + Cache_entry_add + client enqueue + + -//-> + a_Http_dns_cb + Http_connect_socket + OpSend FD, BCK + OpSend FD, FWD + Http_send_query + a_Http_make_query_str + OpSend, BCK + IO_submit + a_IOwatch_add_fd (DIO_WRITE, ...) + + + Note about 'web' structures. They're created using a_Web_new(). +The web.c module keeps a list of valid webs, so anytime you're +unsure of a weak reference to 'web', it can be checked with +a_Web_valid(web). + + + +------------ +Dpi protocol +------------ + + + Query: | + . + 1B --> 1B 1B --> 1B --> | -------------. + .----. .----. .----. . | +I |Capi| |Dpi | | IO | | | + '----' '----' '----' . | + 1F <-- 1F 1F <-- 1F | V + . + | [Server] + . + Answer (same as HTTP): | | + . | + 2B --> 2B 2B --> 2B | | + .----. .----. .----. . | +II |Capi| |Dpi | | IO | | | + '----' '----' '----' . | + 2F <-- 2F 2F <-- 2F <-- | <------------' + . + | + + +CCC Construction: + + a_Capi_open_url() calls a_Capi_dpi_send_cmd() when the URL +belongs to a dpi and it is not cached. + + a_Capi_dpi_send_cmd() builds both the Answer and Query chains +at once (Answer first then Query), in the same way as HTTP does. +Note that the answer chain is the same for both, and the query +chain only differs in the module in the middle ([http] or [dpi]). + + +Function calls for DPI CCC +-------------------------- + + a_Capi_open_url + a_Capi_dpi_send_cmd + Capi OpStart 2B (answer) [Capi] --> [dpi] --> [IO] + Capi OpStart 1B (query) [Capi] --> [http] --> [IO] + a_Cache_open_url + [...] + + +Normal termination: + + When the dpi server is done, it closes the FD, and OpEnd flows +from IO to Capi (answer branch). When in Capi, capi propagates +OpEnd to the query branch. + +Abnormal termination: + + The transfer may be aborted by a_Capi_conn_abort_by_url(). The +OpAbort is not yet standardized and has an ad-hoc implementation. +One idea is to have OpAbort always propagate BCK and then FWD and +to jump into the other chain when it gets to [Capi]. + + +Debugging CCC +------------- + + A simple way to "look" inside it, is to "#define VERBOSE 1" in +chain.c, and then to follow its work with a printed copy of the +diagrams in this document. + + Each new data request generates a CCC, so if you want to debug, +it's good to refine the testcase to the minimum possible number +of connections. + diff --git a/devdoc/Cache.txt b/devdoc/Cache.txt new file mode 100644 index 00000000..4e885df2 --- /dev/null +++ b/devdoc/Cache.txt @@ -0,0 +1,166 @@ + June 2000, --Jcid + Last update: Jul 09 + + ------- + CACHE + ------- + + The cache module is the main abstraction layer between +rendering and networking. + + The capi module acts as a discriminating wrapper which either +calls the cache or the dpi routines depending on the type of +request. + + Every URL must be requested using a_Capi_open_url, which +sends the request to the cache if the data is cached, to dillo's +http module for http: URLs, and through dillo's DPI system for +other URLs. + + Here we'll document non dpi requests. + + + ---------------- + CACHE PHILOSOPHY + ---------------- + + Dillo's cache is very simple; every single resource that's +retrieved (URL) is kept in memory. NOTHING is saved to disk. +This is mainly for three reasons: + + - Dillo encourages personal privacy and it assures there'll be +no recorded tracks of the sites you visited. + + - The Network is full of intermediate transparent proxys that +serve as caches. + + - If you still want to have cached stuff, you can install an +external cache server (such as WWWOFFLE), and benefit from it. + + + --------------- + CACHE STRUCTURE + --------------- + + Currently, dillo's cache code is spread in different sources: +mainly in cache.[ch], dicache.[ch] and it uses some other +functions from mime.c and web.cc. + + Cache.c is the principal source, and it also is the one +responsible for processing cache-clients (held in a queue). +Dicache.c is the interface to the decompressed RGB representations +of currently-displayed images held in DW's imgbuf. + + mime.c and web.cc are used for secondary tasks such as +assigning the right "viewer" or "decoder" for a given URL. + + +---------------- +A bit of history +---------------- + + Some time ago, the cache functions, URL retrieval and +external protocols were a whole mess of mixed code, and it was +getting REALLY hard to fix, improve or extend the functionality. +The main idea of this "layering" is to make code-portions as +independent as possible so they can be understood, fixed, +improved or replaced without affecting the rest of the browser. + + An interesting part of the process is that, as resources are +retrieved, the client (dillo in this case) doesn't know the +Content-Type of the resource at request-time. It only becomes known +when the resource header is retrieved (think of http). This +happens when the cache has control, so the cache sets the +proper viewer for it (unless the Callback function was already +specified with the URL request). + + You'll find a good example in http.c. + + Note: All resources received by the cache have HTTP-style headers. + The file/data/ftp DPIs generate these headers when sending their + non-HTTP resources. Most importantly, a Content-Type header is + generated based on file extension or file contents. + + +------------- +Cache clients +------------- + + Cache clients MUST use a_Capi_open_url to request an URL. The +client structure and the callback-function prototype are defined, +in cache.h, as follows: + +struct _CacheClient { + int Key; /* Primary Key for this client */ + const DilloUrl *Url; /* Pointer to a cache entry Url */ + int Version; /* Dicache version of this Url (0 if not used) */ + void *Buf; /* Pointer to cache-data */ + uint_t BufSize; /* Valid size of cache-data */ + CA_Callback_t Callback; /* Client function */ + void *CbData; /* Client function data */ + void *Web; /* Pointer to the Web structure of our client */ +}; + +typedef void (*CA_Callback_t)(int Op, CacheClient_t *Client); + + + Notes: + + * Op is the operation that the callback is asked to perform + by the cache. { CA_Send | CA_Close | CA_Abort }. + + * Client: The Client structure that originated the request. + + + +-------------------------- +Key-functions descriptions +-------------------------- + +································································ +int a_Cache_open_url(void *Web, CA_Callback_t Call, void *CbData) + + if Web->url is not cached + Create a cache-entry for that URL + Send client to cache queue + else + Feed our client with cached data + +································································ + +---------------------- +Redirections mechanism + (HTTP 30x answers) +---------------------- + + This is by no means complete. It's a work in progress. + + Whenever an URL is served under an HTTP 30x header, its cache +entry is flagged with 'CA_Redirect'. If it's a 301 answer, the +additional 'CA_ForceRedirect' flag is also set, if it's a 302 +answer, 'CA_TempRedirect' is also set (this happens inside the +Cache_parse_header() function). + + Later on, in Cache_process_queue(), when the entry is flagged +with 'CA_Redirect' Cache_redirect() is called. + + + + + + + +----------- +Notes +----------- + + The whole process is asynchronous and very complex. I'll try +to document it in more detail later (source is commented). + Currently I have a drawing to understand it; hope the ASCII +translation serves the same as the original. + If you're planning to understand the cache process thoroughly, +write me a note and I will assign higher priority to further +improvement of this doc. + Hope this helps! + + diff --git a/devdoc/Dillo.txt b/devdoc/Dillo.txt new file mode 100644 index 00000000..a63c9588 --- /dev/null +++ b/devdoc/Dillo.txt @@ -0,0 +1,96 @@ +"Eliminate the guesswork and quality goes up." + + + ------- + DILLO + ------- + + These notes are written with a view to make it less hard, not +easier yet ;), to get into Dillo development. + When I first got into it, I was totally unaware of the browser +internals. Now that I've made my way deep into the core of it, +(we rewrote it 90% and modified the rest), is time to write some +documentation, just to make a less steep learning curve for new +developers. + + --Jcid + + + + -------- + OVERVIEW + -------- + + Dillo can be viewed as the sum of five main parts: + + 1.- Dillo Widget: A custom widget, FLTK-based, that holds the +necessary data structures and mechanisms for graphical rendering. +(Described in Dw*.txt, dw*.c files among the sources.) + + 2.- Dillo Cache: Integrated with a signal driven Input/Output +engine that handles file descriptor activity, the cache acts as +the main abstraction layer between rendering and networking. + Every URL, whether cached or not, must be retrieved using +a_Capi_open_url (Described briefly in Cache.txt, source +contained in capi.c). + IO is described in IO.txt (recommended), source in src/IO/. + + 3.- The HTML parser: A streamed parser that joins the Dillo +Widget and the Cache functionality to make browsing possible +(Described in HtmlParser.txt, source mainly inside html.cc). + + 4.- Image processing code: The part that handles image +retrieval, decoding, caching and displaying. (Described in +Images.txt. Sources: image.c, dw/image.cc, dicache.c, gif.c, +jpeg.c and png.c) + + 5.- The dpi framework: a gateway to interface the browser with +external programs (Example: the bookmarks server plugin). +Dpi spec: http://www.dillo.org/dpi1.html + + + ------------------------- + HOW IS THE PAGE RENDERED? + ------------------------- + +(A short description of the internal function calling process) + + When the user requests a new URL, a_UIcmd_open_url +is queried to do the job; it calls a_Nav_push (The highest level +URL dispatcher); a_Nav_push updates current browsing history and +calls Nav_open_url. Nav_open_url closes all open connections by +calling a_Bw_stop_clients, and then calls +a_Capi_open_url which calls a_Cache_open_url (or the dpi module if +this gateway is used). + + If Cache_entry_search hits (due to a cached url :), the client is +fed with cached data, but if the URL isn't cached yet, a new CCC +(Concomitant Control Chain) is created and committed to fetch the +URL. + + The next CCC link is dynamically assigned by examining the +URL's protocol. It can be a_Http_ccc or a_Dpi_ccc. + + If we have an HTTP URL, a_Http_ccc will succeed, and the http +module will be linked; it will create the proper HTTP query and +link the IO module to submit and deliver the answer. + + Note that as the Content-Type of the URL is not always known +in advance, the answering branch decides where to dispatch it to +upon HTTP header arrival. + + + What happens then? + + Well, the html parser gets fed, and proper functions are +called for each tag (to parse and call the appropriate methods) +and the whole page is contructed in a streamed way. + Somewhere in the middle of it, resize and repaint functions +are activated and idle functions draw to screen what has been +processed. + + (The process for images is described in Images.txt) + + + + diff --git a/devdoc/Dpid.txt b/devdoc/Dpid.txt new file mode 100644 index 00000000..6c418f57 --- /dev/null +++ b/devdoc/Dpid.txt @@ -0,0 +1,331 @@ +Aug 2003, Jorge Arellano Cid, + Ferdi Franceschini -- +Last update: Nov 2009 + + + ------ + dpid + ------ + +------------- +Nomenclature: +------------- + + dpi: + generic term referring to dillo's plugin system (version1). + + dpi1: + specific term for dillo's plugin spec version 1. + at: http://www.dillo.org/dpi1.html + + dpi program: + any plugin program itself. + + dpi framework: + the code base inside and outside dillo that makes dpi1 + working possible (it doesn't include dpi programs). + + dpip: + dillo plugin protocol. The HTML/XML like set of command tags + and information that goes inside the communication sockets. + Note: not yet fully defined, but functional. + Note2: it was designed to be extensible. + + dpid: + dillo plugin daemon. + + server plugin: + A plugin that is capable of accepting connections on a socket. Dpid will + never run more than one instance of a server plugin at a time. + + filter plugin: + A dpi program that reads from stdin and writes to stdout, and that + exits after its task is done (they don't remain as server plugins). + Warning, dpid will run multiple instances of filter plugins if requested. + +----------- +About dpid: +----------- + + * dpid is a program which manages dpi connections. + * dpid is a daemon that serves dillo using IDS sockets. + * dpid launches dpi programs and arranges socket communication + between the dpi program and dillo. + + The concept and motivation is similar to that of inetd. The +plugin manager (dpid) listens for a service request on a socket +and returns the socket/port pair of a plugin that handles the +service. It also watches sockets of inactive plugins and starts +them when a connection is requested. + + +----------------------------------------------------------- +What's the problem with managing dpi programs inside dillo? +----------------------------------------------------------- + + That's the other way to handle it, but it started to show some +problems (briefly outlined here): + + * When having two or more running instances of Dillo, one + should prevail, and take control of dpi managing (but all + dillos carry the managing code). + * If the managing-dillo exits, it must pass control to another + instance, or leave it void if there's no other dillo running! + * The need to synchronize all the running instances of + dillo arises. + * If the controlling instance finishes and quits, all the + dpi-program PIDs are lost. + * Terminating hanged dpis is hard if it's not done with signals + (PIDs) + * Forks can be expensive (Dillo had to fork its dpis). + * When a managing dillo exits, the new one is no longer the + parent of the forked dpis. + * If Unix domain sockets for the dpis were to be named + randomly, it gets very hard to recover their names if the + controlling instance of dillo exits and another must "take + over" the managing. + * It increments dillo's core size. + * If dillo hangs/crashes, dpi activity is lost (e.g. downloads) + * ... + + That's why the managing daemon scheme was chosen. + + +---------------------- +What does dpid handle? +---------------------- + + It solves all the above mentioned shortcomings and also can do: + + * Multiple dillos: + dpid can communicate and serve more than one instance + of dillo. + + * Multiple dillo windows: + two or more windows of the same dillo instance accessing dpis + at the same time. + + * Different implementations of the same service + dpi programs ("dpis") are just an implementation of a + service. There's no problem in implementing a different one + for the same service (e.g. downloads). + + * Upgrading a service: + to a new version or implementation without requiring + patching dillo's core or even bringing down the dpid. + + + And finally, being aware that this design can support the +following functions is very helpful: + + SCHEME Example + ------------------------------------------------------------ + * "one demand/one response" man, preferences, ... + * "resident while working" downloads, mp3, ... + * "resident until exit request" bookmarks, ... + + * "one client only" cd burner, ... + * "one client per instance" man, ... + * "multiple clients/one instance" downloads, cookies ... + + +-------- +Features +-------- + * Dpi programs go in: "EPREFIX/dillo/dpi" or "~/.dillo/dpi". The binaries + are named .dpi as "bookmarks.dpi" and .filter.dpi as in + "hello.filter.dpi". The ".filter" plugins simply read from stdin + and write to stdout. + * Register/update/remove dpis from list of available dpis when a + 'register_all' command is received. + * dpid terminates when it receives a 'DpiBye' command. + * dpis can be terminated with a 'DpiBye' command. + * dpidc control program for dpid, currently allows register and stop. + + +----- +todo: +----- + + These features are already designed, waiting for implementation: + + * dpidc remove // May be not necessary after all... + + +----------------- +How does it work? +----------------- + +o on startup dpid reads dpidrc for the path to the dpi directory + (usually EPREFIX/lib/dillo/dpi). ~/.dillo/dpi is scanned first. + +o both directories are scanned for the list of available plugins. + ~/.dillo/dpi overrides system-wide dpis. + +o next it creates internet domain sockets for the available plugins and + then listens for service requests on its own socket, + and for connections to the sockets of inactive plugins. + +o dpid returns the port of a plugin's socket when a client (dillo) + requests a service. + +o if the requested plugin is a 'server' then + 1) dpid stops watching the socket for activity + 2) forks and starts the plugin + 3) resumes watching the socket when the plugin exits + +o if the requested plugin is a 'filter' then + 1) dpid accepts the connection + 2) maps the socket fd to stdin/stdout (with dup2) + 3) forks and starts the plugin + 4) continues to watch the socket for new connections + + + + +--------------------------- +dpi service process diagram +--------------------------- + + These drawings should be worth a thousand words! :) + + +(I) + .--- s1 s2 s3 ... sn + | + [dpid] [dillo] + | + '--- srs + + The dpid is running listening on several sockets. + + +(II) + .--- s1 s2 s3 ... sn + | + [dpid] [dillo] + | | + '--- srs ------------------' + + dillo needs a service so it connects to the service request + socket of the dpid (srs) and asks for the socket name of the + required plugin (using dpip). + + +(III) + .--- s1 s2 s3 ... sn + | | + [dpid] | [dillo] + | | | + '--- srs '---------------' + + then it connects to that socket (s3, still serviced by dpid!) + + +(IV) + .--- s1 s2 s3 ... sn + | | + .[dpid] | [dillo] + . | | | + . '--- srs '---------------' + . + .............[dpi program] + + when s3 has activity (incoming data), dpid forks the dpi + program for it... + + +(V) + .--- s1 s2 (s3) ... sn + | + [dpid] [dillo] + | | + '--- srs .---------------' + | + [dpi program] + + ... and lets it "to take over" the socket. + + Once there's a socket channel for dpi and dillo, the whole +communication process takes place until the task is done. When +the dpi program exits, dpid resumes listening on the socket (s3). + + +-------------------------------- +So, how do I make my own plugin? +-------------------------------- + + Maybe the simplest way to get started is to understand a few +concepts and then to use the hands-on method by using/modifying +the hello dpi. It's designed as an example to get developers +started. + + --------- + Concepts: + --------- + + * Dillo plugins work by communicating two processes: dillo + and the dpi. + * The underlying protocol (DPIP) has a uniform API which is + powerful enough for both blocking and nonblocking IO, and + filter or server dpis. + * The simplest example is one-request one-answer (for example + dillo asks for a URL and the dpi sends it). You'll find + this and more complex examples in hello.c + + First, you should get familiar with the hello dpi as a user: + + $dillo dpi:/hello/ + + Once you've played enough with it, start reading the well +commented code in hello.c and start making changes! + + + --------------- + Debugging a dpi + --------------- + + The simplest way is to add printf-like feedback using the MSG* +macros. You can start the dpid by hand on a terminal to force +messages to go there. Filter dpis use sdterr and server dpis +stdout. + + Sometimes more complex dpis need more than MSG*. In this case +you can use gdb like this. + + 1.- Add an sleep(20) statement just after the dpi starts. + 2.- Start dillo and issue a request for your dpi. This will + get your dpi started. + 3.- Standing in the dpi source directory: + ps aux|grep dpi + 4.- Take note of the dpi's PID and start gdb, then: + (gdb) attach + 5.- Continue from there... + + + ------------ + Final Notes: + ------------ + + 1.- If you already understand the hello dpi and want to try +something more advanced: + + * bookmarks.c is a good example of a blocking server + * file.c is an advanced example of a server handling multiple + non-blocking connections with select(). + + 2.- Multiple instances of a filter plugin may be run +concurrently, this could be a problem if your plugin records data +in a file, however it is safe if you simply write to stdout. +Alternatively you could write a 'server' plugin instead as they +are guaranteed not to run concurrently. + + 3.- The hardest part is to try to modify the dpi framework code +inside dillo; you have been warned! It already supports a lot of +functionality, but if you need to do some very custom stuff, try +extending the "chat" command, or asking in dillo-dev. + + + + >>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<< + diff --git a/devdoc/HtmlParser.txt b/devdoc/HtmlParser.txt new file mode 100644 index 00000000..2eb8be63 --- /dev/null +++ b/devdoc/HtmlParser.txt @@ -0,0 +1,124 @@ + October 2001, --Jcid + Last update: Jul 2009 + + --------------- + THE HTML PARSER + --------------- + + + Dillo's parser is more than just a HTML parser, it does XHTML +and plain text also. It has parsing 'modes' that define its +behaviour while working: + + typedef enum { + DILLO_HTML_PARSE_MODE_INIT = 0, + DILLO_HTML_PARSE_MODE_STASH, + DILLO_HTML_PARSE_MODE_STASH_AND_BODY, + DILLO_HTML_PARSE_MODE_BODY, + DILLO_HTML_PARSE_MODE_VERBATIM, + DILLO_HTML_PARSE_MODE_PRE + } DilloHtmlParseMode; + + + The parser works upon a token-grained basis, i.e., the data +stream is parsed into tokens and the parser is fed with them. The +process is simple: whenever the cache has new data, it is +passed to Html_write, which groups data into tokens and calls the +appropriate functions for the token type (tag, space, or word). + + Note: when in DILLO_HTML_PARSE_MODE_VERBATIM, the parser +doesn't try to split the data stream into tokens anymore; it +simply collects until the closing tag. + +------ +TOKENS +------ + + * A chunk of WHITE SPACE --> Html_process_space + + + * TAG --> Html_process_tag + + The tag-start is defined by two adjacent characters: + + first : '<' + second: ALPHA | '/' | '!' | '?' + + Note: comments are discarded ( ) + + + The tag's end is not as easy to find, nor to deal with!: + + 1) The HTML 4.01 sec. 3.2.2 states that "Attribute/value + pairs appear before the final '>' of an element's start tag", + but it doesn't define how to discriminate the "final" '>'. + + 2) '<' and '>' should be escaped as '<' and '>' inside + attribute values. + + 3) The XML SPEC for XHTML states: + AttrValue ::== '"' ([^<&"] | Reference)* '"' | + "'" ([^<&'] | Reference)* "'" + + Current parser honors the XML SPEC. + + As it's a common mistake for human authors to mistype or + forget one of the quote marks of an attribute value; the + parser solves the problem with a look-ahead technique + (otherwise the parser could skip significant amounts of + properly-written HTML). + + + + * WORD --> Html_process_word + + A word is anything that doesn't start with SPACE, that's + outside of a tag, up to the first SPACE or tag start. + + SPACE = ' ' | \n | \r | \t | \f | \v + + +----------------- +THE PARSING STACK +----------------- + + The parsing state of the document is kept in a stack: + + class DilloHtml { + [...] + lout::misc::SimpleVector *stack; + [...] + }; + + struct _DilloHtmlState { + CssPropertyList *table_cell_props; + DilloHtmlParseMode parse_mode; + DilloHtmlTableMode table_mode; + bool cell_text_align_set; + DilloHtmlListMode list_type; + int list_number; + + /* TagInfo index for the tag that's being processed */ + int tag_idx; + + dw::core::Widget *textblock, *table; + + /* This is used to align list items (especially in enumerated lists) */ + dw::core::Widget *ref_list_item; + + /* This is used for list items etc; if it is set to TRUE, breaks + have to be "handed over" (see Html_add_indented and + Html_eventually_pop_dw). */ + bool hand_over_break; + }; + + Basically, when a TAG is processed, a new state is pushed into +the 'stack' and its 'style' is set to reflect the desired +appearance (details in DwStyle.txt). + + That way, when a word is processed later (added to the Dw), all +the information is within the top state. + + Closing TAGs just pop the stack. + + diff --git a/devdoc/IO.txt b/devdoc/IO.txt new file mode 100644 index 00000000..cd62a4f5 --- /dev/null +++ b/devdoc/IO.txt @@ -0,0 +1,468 @@ + +This is the updated base of a paper I wrote with Horst. +It provides a good introduction to Dillo's internals. +(Highly recommended if you plan to patch or develop in Dillo) + +It may not be exactly accurate (it's quite old), but it explains +the theory behind in some detail, so it's more than recommended +reading material. +--Jcid + + +----------------------------------------------------- +Parallel network programming of the Dillo web browser +----------------------------------------------------- + + Jorge Arellano-Cid + Horst H. von Brand + + +-------- +Abstract +-------- + + Network programs face several delay sources when sending or +retrieving data. This is particularly problematic in programs +which interact directly with the user, most notably web browsers. +We present a hybrid approach using threads communicated through +pipes and signal driven I/O, which allows a non-blocking main +thread and overlapping waiting times. + + +------------ +Introduction +------------ + + The Dillo project didn't start from scratch but mainly working +on the code base of gzilla (a light web browser written by Raph +Levien). As the project went by, the code of the whole source was +standardized, and the networking engine was replaced with a new, +faster design. Later, the parser was changed, the streamed +handling of data and its error control was put under the control +of the CCC (Concomitant Control Chain), and the old widget system +was replaced with a new one (Dw). The source code is currently +regarded as "very stable beta", and is available at +. Dillo is a project licensed under the GNU +General Public License. + + This paper covers basic design aspects of the hybrid approach +that the Dillo web browser uses to solve several latency +problems. After introducing the main delay-sources, the main +points of the hybrid design will be addressed. + + +------------- +Delay sources +------------- + + Network programs face several delay-sources while sending or +retrieving data. In the particular case of a web browser, they +are found in: + + DNS querying: + The time required to solve a name. + + Initiating the TCP connection: + The three way handshake of the TCP protocol. + + Sending the query: + The time spent uploading queries to the remote server. + + Retrieving data: + The time spent expecting and receiving the query answer. + + Closing the TCP connection: + The four packet-sending closing sequence of the TCP protocol. + + In a WAN context, every single item of this list has an +associated delay that is non deterministic and often measured in +seconds. If we add several connections per browsed page (each one +requiring at least the 4 last steps), the total latency can be +considerable. + + +----------------------------------- +The traditional (blocking) approach +----------------------------------- + + The main problems with the blocking approach are: + + When issuing an operation that can't be completed + immediately, the process is put to sleep waiting for + completion, and the program doesn't do any other + processing in the meantime. + + When waiting for a specific socket operation to complete, + packets that belong to other connections may be arriving, + and have to wait for service. + + Web browsers handle many small transactions, + if waiting times are not overlapped + the latency perceived by the user can be very annoying. + + If the user interface is just put to sleep during network + operations, the program becomes unresponsive, confusing + and perhaps alarming the user. + + Not overlapping waiting times and processing makes + graphical rendering (which is arguably the central function + of a browser) unnecessarily slow. + + +--------------------- +Dillo's hybrid design +--------------------- + + Dillo uses threads and signal driven I/O extensively to +overlap waiting times and computation. Handling the user +interface in a thread that never blocks gives a good interactive +``feel.'' The use of GTK+, a sophisticated widget framework for +graphical user interfaces, helped very much to accomplish this +goal. All the interface, rendering and I/O engine was built upon +its facilities. + + The design is said to be ``hybrid'' because it uses threads +for DNS querying and reading local files, and signal driven I/O +for TCP connections. The threaded DNS scheme is potentially +concurrent (this depends on underlying hardware), while the I/O +handling (both local files and remote connections) is +definitively parallel. + + To simplify the structure of the browser, local files are +encapsulated into HTTP streams and presented to the rest of the +browser as such, in exactly the same way a remote connection is +handled. To create this illusion, a thread is launched. This +thread opens a pipe to the browser, it then synthesizes an +appropriate HTTP header, sends it together with the file to the +browser proper. In this way, all the browser sees is a handle, +the data on it can come from a remote connection or from a local +file. + + To handle a remote connection is more complex. In this case, +the browser asks the cache manager for the URL. The name in the +URL has to be resolved through the DNS engine, a socket TCP +connection must be established, the HTTP request has to be sent, +and finally the result retrieved. Each of the steps mentioned +could give rise to errors, which have to be handled and somehow +communicated to the rest of the program. For performance reasons, +it is critical that responses are cached locally, so the remote +connection doesn't directly hand over the data to the browser; +the response is passed to the cache manager which then relays it +to the rest of the browser. The DNS engine caches DNS responses, +and either answers them from the cache or by querying the DNS. +Querying is done in a separate thread, so that the rest of the +browser isn't blocked by long waits here. + + The activities mentioned do not happen strictly in the order +stated above. It is even possible that several URLs are being +handled at the same time, in order to overlap waiting and +downloading. The functions called directly from the user +interface have to return quickly to maintain interactive +response. Sometimes they return connection handlers that haven't +been completely set up yet. As stated, I/O is signal-driven, when +one of the descriptors is ready for data transfer (reading or +writing), it wakes up the I/O engine. + + Data transfer between threads inside the browser is handled by +pipes, shared memory is little used. This almost obviates the +need for explicit synchronization, which is one of the main areas +of complexity and bugs in concurrent programs. Dillo handles its +threads in a way that its developers can think of it as running +on a single thread of control. This is accomplished by making the +DNS engine call-backs happen within the main thread, and by +isolating file loading with pipes. + + Using threads in this way has three big advantages: + + The browser doesn't block when one of its child threads + blocks. In particular, the user interface is responsive + even while resolving a name or downloading a file. + + Developers don't need to deal with complex concurrent + concerns. Concurrency is hard to handle, and few developers + are adept at this. This gives access a much larger pool of + potential developers, something which can be critical + in an open-source development project. + + By making the code mostly sequential, debugging the code + with traditional tools like gdb is possible. Debugging + parallel programs is very hard, and appropriate tools are + hard to come by. + + Because of simplicity and portability concerns, DNS querying +is done in a separate thread. The standard C library doesn't +provide a function for making DNS queries that don't block. The +alternative is to implement a new, custom DNS querying function +that doesn't block. This is certainly a complex task, integrating +this mechanism into the thread structure of the program is much +simpler. + + Using a thread and a pipe to read a local file adds a +buffering step to the process (and a certain latency), but it has +a couple of significative advantages: + + By handling local files in the same way as remote + connections, a significant amount of code is reused. + + A preprocessing step of the file data can be added easily, + if needed. In fact, the file is encapsulated into an HTTP + data stream. + + +----------- +DNS queries +----------- + + Dillo handles DNS queries with threads, letting a child thread +wait until the DNS server answers the request. When the answer +arrives, a call-back function is called, and the program resumes +what it was doing at DNS-request time. The interesting thing is +that the call-back happens in the main thread, while the child +thread simply exits when done. This is implemented through a +server-channel design. + + +The server channel +------------------ + + There is one thread for each channel, and each channel can +have multiple clients. When the program requests an IP address, +the server first looks for a cached match; if it hits, the client +call-back is invoked immediately, but if not, the client is put +into a queue, a thread is spawned to query the DNS, and a GTK+ +idle client is set to poll the channel 5~times per second for +completion, and when it finally succeeds, every client of that +channel is serviced. + + This scheme allows all the further processing to continue on +the same thread it began: the main thread. + + +------------------------ +Handling TCP connections +------------------------ + + Establishing a TCP connection requires the well known +three-way handshake packet-sending sequence. Depending on network +traffic and several other issues, significant delay can occur at +this phase. + + Dillo handles the connection by a non blocking socket scheme. +Basically, a socket file descriptor of AF_INET type is requested +and set to non-blocking I/O. When the DNS server has resolved the +name, the socket connection process begins by calling connect(2); + {We use the Unix convention of identifying the manual section + where the concept is described, in this case + section 2 (system calls).} +which returns immediately with an EINPROGRESS error. + + After the connection reaches the EINPROGRESS ``state,'' the +socket waits in background until connection succeeds (or fails), +when that happens, a callback function is awaked to perform the +following steps: set the I/O engine to send the query and expect +its answer (both in background). + + The advantage of this scheme is that every required step is +quickly done without blocking the browser. Finally, the socket +will generate a signal whenever I/O is possible. + + +---------------- +Handling queries +---------------- + + In the case of a HTTP URL, queries typically translate into a +short transmission (the HTTP query) and a lengthy retrieval +process. Queries are not always short though, specially when +requesting forms (all the form data is attached within the +query), and also when requesting CGI programs. + + Regardless of query length, query sending is handled in +background. The thread that was initiated at TCP connecting time +has all the transmission framework already set up; at this point, +packet sending is just a matter of waiting for the +write signal (G_IO_OUT) to come and then sending the data. When +the socket gets ready for transmission, the data is sent using +IO_write. + + +-------------- +Receiving data +-------------- + + Although conceptually similar to sending queries, retrieving +data is very different as the data received can easily exceed the +size of the query by many orders of magnitude (for example when +downloading images or files). This is one of the main sources of +latency, the retrieval can take several seconds or even minutes +when downloading large files. + + The data retrieving process for a single file, that began by +setting up the expecting framework at TCP connecting time, simply +waits for the read signal (G_IO_IN). When it happens, the +low-level I/O engine gets called, the data is read into +pre-allocated buffers and the appropriate call-backs are +performed. Technically, whenever a G_IO_IN event is generated, +data is received from the socket file descriptor, by using the +IO_read function. This iterative process finishes upon EOF (or on +an error condition). + + +---------------------- +Closing the connection +---------------------- + + Closing a TCP connection requires four data segments, not an +impressive amount but twice the round trip time, which can be +substantial. When data retrieval finishes, socket closing is +triggered. There's nothing but a IO_close_fd call on the socket's +file descriptor. This process was originally designed to split +the four segment close into two partial closes, one when query +sending is done and the other when all data is in. This scheme is +not currently used because the write close also stops the reading +part. + + +The low-level I/O engine +------------------------ + + Dillo I/O is carried out in the background. This is achieved +by using low level file descriptors and signals. Anytime a file +descriptor shows activity, a signal is raised and the signal +handler takes care of the I/O. + + The low-level I/O engine ("I/O engine" from here on) was +designed as an internal abstraction layer for background file +descriptor activity. It is intended to be used by the cache +module only; higher level routines should ask the cache for its +URLs. Every operation that is meant to be carried out in +background should be handled by the I/O engine. In the case of +TCP sockets, they are created and submitted to the I/O engine for +any further processing. + + The submitting process (client) must fill a request structure +and let the I/O engine handle the file descriptor activity, until +it receives a call-back for finally processing the data. This is +better understood by examining the request structure: + + typedef struct { + gint Key; /* Primary Key (for klist) */ + gint Op; /* IORead | IOWrite | IOWrites */ + gint FD; /* Current File Descriptor */ + gint Flags; /* Flag array */ + glong Status; /* Number of bytes read, or -errno code */ + + void *Buf; /* Buffer place */ + size_t BufSize; /* Buffer length */ + void *BufStart; /* PRIVATE: only used inside IO.c! */ + + void *ExtData; /* External data reference (not used by IO.c) */ + void *Info; /* CCC Info structure for this IO */ + GIOChannel *GioCh; /* IO channel */ + guint watch_id; /* glib's event source id */ + } IOData_t; + + To request an I/O operation, this structure must be filled and +passed to the I/O engine. + + 'Op' and 'Buf' and 'BufSize' MUST be provided. + + 'ExtData' MAY be provided. + + 'Status', 'FD' and 'GioCh' are set by I/O engine internal +routines. + + When there is new data in the file descriptor, 'IO_callback' +gets called (by glib). Only after the I/O engine finishes +processing the data are the upper layers notified. + + +The I/O engine transfer buffer +------------------------------ + + The 'Buf' and 'BufSize' fields of the request structure +provide the transfer buffer for each operation. This buffer must +be set by the client (to increase performance by avoiding copying +data). + + On reads, the client specifies the amount and where to place +the retrieved data; on writes, it specifies the amount and source +of the data segment that is to be sent. Although this scheme +increases complexity, it has proven very fast and powerful. For +instance, when the size of a document is known in advance, a +buffer for all the data can be allocated at once, eliminating the +need for multiple memory reallocations. Even more, if the size is +known and the data transfer is taking the form of multiple small +chunks of data, the client only needs to update 'Buf' and +BufSize' to point to the next byte in its large preallocated +reception buffer (by adding the chunk size to 'Buf'). On the +other hand, if the size of the transfer isn't known in advance, +the reception buffer can remain untouched until the connection +closes, but the client must then accomplish the usual buffer +copying and reallocation. + + The I/O engine also lets the client specify a full length +transfer buffer when sending data. It doesn't matter (from the +client's point of view) if the data fits in a single packet or +not, it's the I/O engine's job to divide it into smaller chunks +if needed and to perform the operation accordingly. + + +------------------------------------------ +Handling multiple simultaneous connections +------------------------------------------ + + The previous sections describe the internal work for a single +connection, the I/O engine handles several of them in parallel. +This is the normal downloading behavior of a web page. Normally, +after retrieving the main document (HTML code), several +references to other files (typically images) and sometimes even +to other sites (mostly advertising today) are found inside the +page. In order to parse and complete the page rendering, those +other documents must be fetched and displayed, so it is not +uncommon to have multiple downloading connections (every one +requiring the whole fetching process) happening at the same time. + + Even though socket activity can reach a hectic pace, the +browser never blocks. Note also that the I/O engine is the one +that directs the execution flow of the program by triggering a +call-back chain whenever a file descriptor operation succeeds or +fails. + + A key point for this multiple call-back chained I/O engine is +that every single function in the chain must be guaranteed to +return quickly. Otherwise, the whole system blocks until it +returns. + + +----------- +Conclusions +----------- + + Dillo is currently in very stable beta state. It already shows +impressive performance, and its interactive ``feel'' is much +better than that of other web browsers. + + The modular structure of Dillo, and its reliance on GTK1 allow +it to be very small. Not every feature of HTML-4.01 has been +implemented yet, but no significant problems are foreseen in +doing this. + + The fact that Dillo's central I/O engine is written using +advanced features of POSIX and TCP/IP networking makes its +performance possible, but on the other hand this also means that +only a fraction of the interested hackers are able to work on it. + + A simple code base is critical when trying to attract hackers +to work on a project like this one. Using the GTK+ framework +helped both in creating the graphical user interface and in +handling the concurrency inside the browser. By having threads +communicate through pipes the need for explicit synchronization +is almost completely eliminated, and with it most of the +complexity of concurrent programming disappears. + + A clean, strictly applied layering approach based on clear +abstractions is vital in each programming project. A good, +supportive framework is of much help here. + + diff --git a/devdoc/Images.txt b/devdoc/Images.txt new file mode 100644 index 00000000..62082e48 --- /dev/null +++ b/devdoc/Images.txt @@ -0,0 +1,129 @@ + January 2009, --Jcid + +Update June 2015: See also doc/dw-images-and-backgrounds.doc, or +../html/dw-images-and-backgrounds.html (generated by doxygen). + + ------ + IMAGES + ------ + +* When a image tag is found within a HTML page, Html_tag_open_img +handles it by: + + - Parsing & getting attribute values. + - Creating a new image structure (DilloImage) and its + associated widget (DwImage). + i.e. If 'Image' is the var for the structure, then + 'Image->dw' is the widget. + - Requesting the image to be feeded by the cache. + - Sending some info to the browser interface. + +* The cache can either request the image data from the net, or +feed it directly from the dicache (decompressed image cache). + +* Both processes are somewhat different because the first one +requires to decode the image data into RGB format, and the second +one has the whole data already decoded. + +* Regardless of the RGB-data feeding method, the decoded data is +passed to the widget (DwImage) and drawn in a streamed way. + Note that INDEXED images are also decoded into RGB format. + + Html_tag_open_img // IMG element processing + Html_add_new_image // Read attributes, create image, add to HTML page + a_Image_new // Create a 'DilloImage' data structure, to coordinate + // decoded image-data transfer to an 'Imgbuf'. + Html_add_widget // Adds the dw::Image to the page + Html_load_image // Tells cache to retrieve image + + +--------------------- +Fetching from the net +--------------------- + +* a_Capi_open_url initiates the resource request, and when +finally the answer arrives, the HTTP header is examined for MIME +type and either the GIF or PNG or JPEG decoder is set to handle +the incoming data stream. + + Decoding functions: + a_Gif_callback, a_Jpeg_callback and a_Png_callback. + +* The decoding function calls the following dicache methods as +the data is processed (listed in order): + + a_Dicache_set_parms + a_Dicache_set_cmap (only for indexed-GIF images) + a_Dicache_write + a_Dicache_new_scan (MAY be called here or after set_cmap) + a_Dicache_close + + +* The dicache methods call the necessary functions to connect +with the widget code. This is done by calling image.c functions: + + a_Image_set_parms + a_Image_set_cmap + a_Image_write + a_Image_new_scan + a_Image_close + +* The functions in image.c make the required Dw calls. + + +------------------------- +Fetching from the dicache +------------------------- + +* a_Capi_open_url() tests the cache for the image, and the cache, +via a_Cache_open_url(), enqueues a client for it, without asking +the network for the data. When the client queue is processed (a +bit later), Dicache_image() is set as the callback. + +* When Dicache_image() is called, it sets the proper image data +decoder (RGB) and its data structure based on the entry's Type. +Then it substitutes itself with a_Dicache_callback() as the +handling function, and gets out of the way. + +* Thenceforth the rest of the functions calls is driven by +a_Dicache_callback(). + + +----------- +Misc. notes +----------- + +* Repeated images generate new cache clients, but they may share +the imgbuf. + Note: Currently there's no proper support for transparent +images (i.e. decode to RGBA), but most of the time they render +the background color OK. This is: when first loaded, repeated +images share a background color, but when cached they render +correctly ;-). There's no point in trying to fix this because the +correct solution is to decode to RGBA and let the toolkit (FLTK) +handle the transparency. + +* The first cache-client callback (Dicache_image()) is set when +the Content-type of the image is got. + +* Later on, when there's a shared imgbuf, the dicache's logic +avoids decoding it multiple times and reuses what's already done. + +* The dicache-entry and the Image structure hold bit arrays that +represent which rows have been decoded. + +* The image processing can be found in the following sources: + + - image.{cc,hh} + - dicache.[ch] + - gif.[ch], png.[ch], jpeg.[ch] + - dw/image.{cc,hh} + +* Bear in mind that there are four data structures for image +code: + + - DilloImage (image.hh) + - DICacheEntry (dicache.h) + - dw::Image (class Image in dw/image.hh) + - core::Imgbuf (imgbuf.hh) + diff --git a/devdoc/NC_design.txt b/devdoc/NC_design.txt new file mode 100644 index 00000000..380787f6 --- /dev/null +++ b/devdoc/NC_design.txt @@ -0,0 +1,127 @@ + + _________________________________________________________________ + + Naming&Coding design + _________________________________________________________________ + + Dillo's code is divided into modules. For instance: bookmark, cache, + dicache, gif. + + Let's think of a module named "menu", then: + * Every internal routine of the module, should start with "Menu_" + prefix. + * "Menu_" prefixed functions are not meant to be called from outside + the module. + * If the function is to be exported to other modules (i.e. it will + be called from the outside), it should be wrapped with an "a_" + prefix. + + For instance: if the function name is "Menu_create", then it's an + internal function, but if we need to call it from the outside, then it + should be renamed to "a_Menu_create". + + Why the "a_" prefix? + Because of historical reasons. + And "a_Menu_create" reads better than "d_Menu_create" because the + first one suggests "a Menu create" function! + + Another way of understanding this is thinking of "a_" prefixed + functions as Dillo's internal library, and the rest ("Menu_" prefixed + in our example) as a private module-library. + + Indentation: + + Source code must be indented with 3 blank spaces, no Tabs. + Why? + Because different editors expand or treat tabs in several ways; 8 + spaces being the most common, but that makes code really wide and + we'll try to keep it within the 80 columns bounds (printer friendly). + + You can use: indent -kr -sc -i3 -bad -nbbo -nut -l79 myfile.c + + Function commenting: + + Every single function of the module should start with a short comment + that explains its purpose; three lines must be enough, but if you + think it requires more, enlarge it. + + /* + * Try finding the url in the cache. If it hits, send the contents + * to the caller. If it misses, set up a new connection. + */ + int a_Cache_open_url(const char *url, void *Data) + { + ... + ... + ... + } + + We also have the BUG:, TODO:, and WORKAROUND: tags. + Use them within source code comments to spot hidden issues. For + instance: + + /* BUG: this counter is not accurate */ + ++i; + + /* TODO: get color from the right place */ + a = color; + + /* WORKAROUND: the canonical way of doing it doesn't work yet. */ + ++a; ++a; ++a; + + Function length: + + Let's try to keep functions within the 45 lines boundary. This eases + code reading, following, understanding and maintenance. + + Functions with a single exit: + + It's much easier to follow and maintain functions with a single exit + point at the bottom (instead of multiple returns). The exception to + the rule are calls like dReturn_if_fail() at its head. + + dlib functions: + + * Dillo uses dlib extensively in its C sources. Before starting + to code something new, a good starting point is to check what + this library has to offer (check dlib/dlib.h). + * Memory management must be done using dNew, dNew0, dMalloc, dFree + and their relatives. + * For debugging purposes and error catching (not for normal flow): + dReturn_if_fail, dReturn_val_if_fail etc. are encouraged. + * The MSG macro is extensively used to output additional information + to the calling terminal. + + _________________________________________________________________ + + C++ + + Source code in C++ should follow the same rules with these exceptions: + + * Class method names are camel-cased and start with lowercase + e.g. appendInputMultipart + * Classes and types start uppercased + e.g. class DilloHtmlReceiver + * Class methods don't need to prefix its module name + e.g. links->get() + + We also try to keep the C++ relatively simple. Dillo does use + inheritance and templates, but that's about all. + + _________________________________________________________________ + + What do we get with this? + + * A clear module API for Dillo; every function prefixed "a_" is to + be used outside the module. + * A way to identify where the function came from (the + capitalized word is the module name). + * An inner ADT (Abstract data type) for the module that can be + isolated, tested and replaced independently. + * A two stage instance for bug-fixing. You can change the exported + function algorithms while someone else fixes the internal + module-ADT! + * A coding standard ;) + _________________________________________________________________ + + Naming&Coding design by Jorge Arellano Cid diff --git a/devdoc/README b/devdoc/README new file mode 100644 index 00000000..9736a32b --- /dev/null +++ b/devdoc/README @@ -0,0 +1,51 @@ +README: Last update Jul 2009 + +These documents cover dillo's internals. +For user help, see http://www.dillo.org/dillo3-help.html + +-------------------------------------------------------------------------- + +These documents need a review. +*.txt were current with Dillo1, but many have since become more or + less out-of-date. +*.doc are doxygen source for the Dillo Widget (dw) component, and + were written for Dillo2. + +They will give you an overview of what's going on, but take them +with a pinch of salt. + + Of course I'd like to have *.txt as doxygen files too! +If somebody wants to make this conversion, please let me know +to assign higher priority to updating these docs. + +-- +Jorge.- + + -------------------------------------------------------------------------- + FILE DESCRIPTION STATE + -------------------------------------------------------------------------- + NC_design.txt Naming&Coding design (Be sure to Current + read it before any other doc) + Dillo.txt General overview of the program Current + IO.txt Extensive introduction Current + Cache.txt Informative description Current + Images.txt Image handling and processing Current + HtmlParser.txt A versatile parser Current + Dw.txt The New Dillo Widget (Overview) Current + Imgbuf.txt Image buffers Pending + Selection.txt Selections, and link activation Current (?) + Cookies.txt Explains how to enable cookies Current + Dpid.txt Dillo plugin daemon Current + -------------------------------------------------------------------------- + + + * BTW, there's a small program (srch) within the src/ dir. It searches + tokens within the whole code (*.[ch]). It has proven very useful. + Ex: ./srch a_Image_write + ./srch todo: + + * Please submit your patches with 'hg diff'. + + + Happy coding! + --Jcid diff --git a/devdoc/dw-changes.doc b/devdoc/dw-changes.doc new file mode 100644 index 00000000..7050df9a --- /dev/null +++ b/devdoc/dw-changes.doc @@ -0,0 +1,105 @@ +/** \page dw-changes Changes to the GTK+-based Release Version + +

Changes in Dw

+ +Related to the FLTK port, there have been many changes, this is a +(hopefully complete) list: + +
    +
  • Rendering abstraction, read \ref dw-overview and \ref dw-layout-views + for details. Some important changes: + +
      +
    • The underlying platform (e.g. the UI toolkit) is fully abstract, + there are several platform independent structures replacing + GTK+ structures, e.g. dw::core::Event. + +
    • The central class managing the widget tree is not anymore + GtkDwViewport, but dw::core::Layout. + +
    • Drawing is done via dw::core::View, a pointer is passed to + dw::core::Widget::draw. + +
    • The distinction between viewport coordinates and canvas + coordinates (formerly world coordinates) has been mostly + removed. (Only for views, it sometimes plays a role, see + \ref dw-layout-views). +
    + +
  • Cursors have been moved to dw::core::style, see + dw::core::style::Style::cursor. dw::core::Widget::setCursor is now + protected (and so only called by widget implementations). + +
  • World coordinates are now called canvas coordinates. + +
  • There is now a distinction between dw::core::style::StyleAttrs and + dw::core::style::Style. + +
  • There is no base class for container widgets anymore. The former + DwContainer::for_all has been removed, instead this functionality + is now done via iterators (dw::core::Widget::iterator, + dw::core::Iterator). + +
  • DwPage is now called dw::Textblock, and DwAlignedPage + dw::AlignedTextblock. + +
  • dw::Textblock, all sub classes of it, and dw::Table do not read + "limit_text_width" from the preferences, but get it as an argument. + (May change again.) + +
  • dw::Table has been rewritten. + +
  • Instead of border_spacing in the old DwStyle, there are two attributes, + dw::core::style::Style::hBorderSpacing and + dw::core::style::Style::vBorderSpacing, since CSS allowes to specify + two values. Without CSS, both attributes should have the same value. + +
  • Images are handled differently, see \ref dw-images-and-backgrounds. + +
  • Embedded UI widgets (formerly GtkWidget's) are handled differently, + see dw::core::ui. + +
  • DwButton has been removed, instead, embedded UI widgets are used. See + dw::core::ui and dw::core::ui::ComplexButtonResource. +
+ +Dw is now written C++, the transition should be obvious. All "Dw" +prefixes have been removed, instead, namespaces are used now: + +
    +
  • dw::core contains the core, +
  • dw::core::style styles, +
  • dw::core::ui embedded UI resources, +
  • dw::fltk classes related to FLTK, and +
  • ::dw the widgets. +
+ +

Documentation

+ +The old documentation has been moved to: + + +
Old New +
Dw.txt + general part \ref dw-overview, \ref dw-usage, + \ref dw-layout-widgets, + \ref dw-widget-sizes +
remarks on specific widgets respective source files: dw::Bullet, + dw::core::ui::Embed +
DwImage.txt + signals dw::core::Layout::LinkReceiver +
rest dw::Image, + \ref dw-images-and-backgrounds +
Imgbuf.txt dw::core::Imgbuf, + \ref dw-images-and-backgrounds +
DwPage.txt dw::Textblock +
DwRender.txt \ref dw-overview, \ref dw-layout-views, + dw::core::ui +
DwStyle.txt dw::core::style +
DwTable.txt dw::Table +
DwWidget.txt dw::core::Widget, \ref dw-layout-widgets, + \ref dw-widget-sizes +
Selection.txt dw::core::SelectionState +
+ +*/ diff --git a/devdoc/dw-example-screenshot.png b/devdoc/dw-example-screenshot.png new file mode 100644 index 00000000..94f272ab Binary files /dev/null and b/devdoc/dw-example-screenshot.png differ diff --git a/devdoc/dw-floats-01.png b/devdoc/dw-floats-01.png new file mode 100644 index 00000000..116d36b3 Binary files /dev/null and b/devdoc/dw-floats-01.png differ diff --git a/devdoc/dw-grows.doc b/devdoc/dw-grows.doc new file mode 100644 index 00000000..a0304ef9 --- /dev/null +++ b/devdoc/dw-grows.doc @@ -0,0 +1,202 @@ +/** \page dw-grows GROWS - Grand Redesign Of Widget Sizes + +This paper describes (will describe) some design changes to +calculating widget sizes. Goals are: + +- Simplification of widget size calculation by the parent widget; + dw::Textblock::calcWidgetSize, dw::OutOfFlowMgr::ensureFloatSize + etc. should become simpler or perhaps even obsolete. + +- Making the implementation of some features possible: + + - *max-width*, *max-height*, *min-width*, *min-height*; + - correct aspect ratio for images with only one percentage size defined; + - *display: inline-block*; + - <button>. + + +A short sketch +============== + +**dw::core::Widget::sizeRequest and dw::core::Widget::getExtremes will +return final results.** The caller does not have to correct the size, +e. g. when percentages are defined. As an example, +dw::Textblock::calcWidgetSize has already become much simpler. + +**A new hierarchy, *container*:** Aside from dw::core::Widget::parent +and dw::core::Widget::generator, there is a third hierarchy +dw::core::Widget::container, which is (unlike *generator*) always a +direct ancestor, and represents what in CSS is called *containing +block*. Containers are important to define the "context size", which +is (not solely) used for percentage sizes. + +(There is another "containing block", dw::Textblock::containingBlock; +these may be consolidated some day.) + +**The process of size calculation is split between the widget itself +and its container:** + +- The container provides some abstract methods: + dw::core::Widget::getAvailWidthOfChild, + dw::core::Widget::getAvailHeightOfChild, + dw::core::Widget::correctRequisitionOfChild, and + dw::core::Widget::correctExtremesOfChild, which can be used in the + actual implementation of dw::core::Widget::sizeRequestImpl; + different containers with different ways how to arrange their + children will implement these methods in a different way. (Simple + example: the *available width* for children within a textblock is + the *available width* for the textblock itself, minus + margin/border/padding; on the other hand, it is completely different + for children of tables, for which a complex column width calculation + is used.) + +- The actual size calculation is, however, controlled by the widget + itself, which only *uses* these methods above. + +
+ Update: This is not fully correct; the parents are also involved + for calculating available widths and heights, at least when CSS 'width' + and 'height' are not set.
+ +**Size hints are removed.** Instead, the container methods in the +previous paragraph are used. Changes of container sizes (especially +viewport the size) are handled in a different way. + +**Extremes are extended by intrinsic values.** In some cases (see +dw::Table::forceCalcCellSizes, case *minWidth* > *totalWidth*, for an +example) it is useful to know about minimal and maximal width of a +widget independent of CSS attributes. For this, dw::core::Extremes is +extended by: + +- dw::core::Extremes::minWidthIntrinsic and +- dw::core::Extremes::maxWidthIntrinsic. + +The rules for the calculation: + +1. If a widget has no children, it calculates *minWidthIntrinsic* and + *maxWidthIntrinsic* as those values not affected by CSS hints. + (dw::core::Widget::correctExtremes will not change these values.) +2. A widget must calculate *minWidthIntrinsic* and *maxWidthIntrinsic* + from *minWidthIntrinsic* and *maxWidthIntrinsic* of its children, + and *minWidth* and *maxWidth* from *minWidth* and *maxWidth* of its + children. +3. At the end, *minWidth* and *maxWidth* of a widget are corrected by + CSS attributes. (dw::core::Widget::correctExtremes will do this.) + +
+ Notice: Currently, dw::core::Widget::getExtremesImpl must + set all four members in dw::core::Extremes; this may change.
+ +Another **extension of extremes: *adjustmentWidth*.** This is used as +minimum for the width, when "adjust_min_width" (or, +"adjust_table_min_width", respectively) is set. + +The rules for the calculation: + +1. If a widget has no children, it can choose a suitable value, + typically based on dw::core::Extremes::minWidth and + dw::core::Extremes::minWidthIntrinsic. +2. A widget must calculate *adjustmentWidth* from *adjustmentWidth* of + its children. + +*Note:* An implementation of dw::core::Widget::getExtremesImpl may set +this value *after* calling dw::core::Widget::correctExtremesOfChild, +so that it cannot be used for the correction of extremes. In this case +*useAdjustmentWidth = false* should be passed to +dw::core::Widget::correctExtremesOfChild. On the other hand, if known +before, *useAdjustmentWidth* should be set to *true*. + +Rules for *new* methods related to resizing +=========================================== + +- Of course, *sizeRequestImpl* may (should) call *correctRequisition*, + and *getExtremesImpl* may (should) call *correctExtremes*. + +- *sizeRequestImpl* (and *correctRequisition*) is allowed to call + *getAvailWidth* and *getAvailHeight* with *forceValue* set, but + *getExtremesImpl* (and *correctExtremes*) is allowed to call these + only with *forceValue* unset. + +- For this reason, *sizeRequestImpl* is indeed allowed to call + *getExtremes* (dw::Table does so), but the opposite + (*getExtremesImpl* calling *sizeRequest*) is not allowed + anymore. (Before GROWS, the standard implementation + dw::core::Widget::getExtremesImpl did so.) + +- Finally, *getAvailWidth* and *getAvailHeight* may call + *getExtremes*, if and only if *forceValue* is set. + +Here is a diagram showing all permitted dependencies: + +\dot +digraph G { + node [shape=record, fontname=Helvetica, fontsize=10, color="#c0c0c0"]; + edge [arrowhead="open", arrowtail="none", color="#404040"]; + + "sizeRequest[Impl]" -> "getExtremes[Impl]"; + "sizeRequest[Impl]" -> correctRequisition; + "getExtremes[Impl]" -> correctExtremes; + "sizeRequest[Impl]" -> "getAvail[Width|Height] (true)"; + "getExtremes[Impl]" -> "getAvail[Width|Height] (false)"; + correctRequisition -> "getAvail[Width|Height] (true)"; + correctExtremes -> "getAvail[Width|Height] (false)"; + "getAvail[Width|Height] (true)" -> "getExtremes[Impl]"; +} +\enddot + +Open issues +=========== + +**Do CSS size dimensions override intrinsic sizes in all cases?** If a +textblock needs at least, say, 100 pixels width so that the text can +be read, but has a specification "width: 50px", should half of the +text be invisible? Or should the width be corrected again to 100 +pixels? + +Currently, in the CSS size specification is honoured in all cases, +with one exception: see dw::Textblock::sizeRequestImpl and see +dw::Textblock::getExtremesImpl (the time when +dw::core::Widget::correctRequisition and +dw::core::Widget::correctExtremes, respectively, is called). + +*Not* honouring the CSS size specification in all cases could improve +readability in some cases, so this could depend on a user preference. + +**Update:** There is now a dillorc option adjust_min_width, +which is implemented for widths, but not heights (since it is based on +width extremes, but there are currently no height extremes). + +Another problem is that in most cases, there is no clippping, so that +contents may exceed the allocation of the widget, but redrawing is not +necessarily triggered. + +**Percentage values for margins and paddings, as well as negative +margins** are interesting applications, but have not been considered +yet. For negative margins, a new attribute +dw::core::Widget::extraSpace could solve the problem of widgets +sticking out of the allocation of parent. + +**Clarify percentage heights.** Search in widget.cc, and compare +section 10.5 ('height') of the CSS 2.1 specification to section 10.2 +('width'). + +**Fast queue resize does not work fully.** Example: run +*test/dw-simple-container-test* (dw_simple_container_test.cc), resize +(best maximize) the window and follow (e. g. by using RTFL) what +happens in consequence of dw::core::Layout::viewportSizeChanged. The +dw::SimpleContainer in the middle is not affected, so only the two +dw::Textblock's (at the top and at the bottom) call queueResize with +*fast = true*, and so get *NEEDS_RESIZE* set; but since it is not set +for the dw::SimpleContainer, *sizeRequest* is never called for the +bottom dw::Textblock. + +There does not seem to be a real case for this problem in dillo, since +all widgets which may contain other widgets (except +dw::SimpleContainer, which is not used outside tests) use the +available width and height (dw::core::Widget::usesAvailWidth and +dw::core::Widget::usesAvailHeight), and so are always affected by +viewport size changes. + +*/ diff --git a/devdoc/dw-images-and-backgrounds.doc b/devdoc/dw-images-and-backgrounds.doc new file mode 100644 index 00000000..8f07766a --- /dev/null +++ b/devdoc/dw-images-and-backgrounds.doc @@ -0,0 +1,235 @@ +/** \page dw-images-and-backgrounds Images and Backgrounds in Dw + +Image Buffers +============= + +Representation of the image data is done by dw::core::Imgbuf, see +there for details. Drawing is done by dw::core::View +(dw::core::View::drawImage). + +Since dw::core::Imgbuf provides memory management based on reference +counting, there may be an 1-to-n relation from image renderers (image +widgets or backgrounds, see below) and dw::core::Imgbuf. Since +dw::core::Imgbuf does not know about renderers, but just provides +rendering functionality, the caller must (typically after calling +dw::core::Imgbuf::copyRow) notify all renderers connected to the +buffer. + + +Image Renderer +============== + +Generally, there are no restrictions on how to manage +dw::core::Imgbuf; but to handle image data from web resources, the +interface dw::core::ImgRenderer should be implemented. It is again +wrapped by DilloImage (to make access from the C part possible, since +dw::core::ImgRenderer is written in C++), which is referenced by +DilloWeb. There are two positions where retrieving image data is +initiated: + +- Html_load_image: for embedded images (implemented by dw::Image, + which implements dw::core::ImgRenderer); +- StyleEngine::apply (search for "case + CSS_PROPERTY_BACKGROUND_IMAGE"): for backgrond images; there are + some implementations of dw::core::ImgRenderer within the context of + dw::core::style::StyleImage. + +Both are described in detail below. Notice that the code is quite +similar; only the implementation of dw::core::ImgRenderer differs. + +At this time, dw::core::ImgRenderer has got two methods (see more +documentation there): + +- dw::core::ImgRenderer::setBuffer, +- dw::core::ImgRenderer::drawRow, +- dw::core::ImgRenderer::finish, and +- dw::core::ImgRenderer::fatal. + + +Images +====== + +This is the simplest renderer, displaying an image. For each row to be +drawn, + +- first dw::core::Imgbuf::copyRow, and then +- for each dw::Image, dw::Image::drawRow must be called, with the same + argument (no scaling is necessary). + +dw::Image automatically scales the dw::core::Imgbuf, the root buffer +should be passed to dw::Image::setBuffer. + +\see dw::Image for more details. + + +Background Images +================= + +Since background images are style resources, they are associated with +dw::core::style::Style, as dw::core::style::StyleImage, which is +handled in a similar way (reference counting etc.) as +dw::core::style::Color and dw::core::style::Font, although it is +concrete and not platform-dependant. + +The actual renderer (passed to Web) is an instance of +dw::core::ImgRendererDist (distributes all calls to a set of other +instances of dw::core::ImgRenderer), which contains two kinds of +renderers: + +- one instance of dw::core::style::StyleImage::StyleImgRenderer, which + does everything needed for dw::core::style::StyleImage, and +- other renderers, used externally (widgets etc.), which are added by + dw::core::style::StyleImage::putExternalImgRenderer (and removed by + dw::core::style::StyleImage::removeExternalImgRenderer). + +This diagram gives an comprehensive overview: + +\dot +digraph G { + node [shape=record, fontname=Helvetica, fontsize=10]; + edge [arrowhead="open", dir="both", arrowtail="none", + labelfontname=Helvetica, labelfontsize=10, color="#404040", + labelfontcolor="#000080"]; + fontname=Helvetica; fontsize=10; + + subgraph cluster_dw_style { + style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + + Style [URL="\ref dw::core::style::Style"]; + StyleImage [URL="\ref dw::core::style::StyleImage"]; + Imgbuf [URL="\ref dw::core::Imgbuf", color="#a0a0a0"]; + StyleImgRenderer + [URL="\ref dw::core::style::StyleImage::StyleImgRenderer"]; + ImgRenderer [URL="\ref dw::core::ImgRenderer", color="#ff8080"]; + ImgRendererDist [URL="\ref dw::core::ImgRendererDist"]; + ExternalImgRenderer + [URL="\ref dw::core::style::StyleImage::ExternalImgRenderer", + color="#a0a0a0"]; + ExternalWidgetImgRenderer + [URL="\ref dw::core::style::StyleImage::ExternalWidgetImgRenderer", + color="#a0a0a0"]; + } + + subgraph cluster_dw_layout { + style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + + Layout [URL="\ref dw::core::Layout"]; + LayoutImgRenderer [URL="\ref dw::core::Layout::LayoutImgRenderer"]; + } + + subgraph cluster_dw_widget { + style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + + Widget [URL="\ref dw::core::Widget", color="#a0a0a0"]; + WidgetImgRenderer [URL="\ref dw::core::Widget::WidgetImgRenderer"]; + } + + subgraph cluster_dw_textblock { + style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + + Textblock [URL="\ref dw::Textblock"]; + Word [URL="\ref dw::Textblock::Word"]; + WordImgRenderer [URL="\ref dw::Textblock::WordImgRenderer"]; + SpaceImgRenderer [URL="\ref dw::Textblock::SpaceImgRenderer"]; + } + + Style -> StyleImage [headlabel="*", taillabel="1"]; + StyleImage -> Imgbuf [headlabel="*", taillabel="1"]; + StyleImage -> StyleImgRenderer [headlabel="1", taillabel="1"]; + StyleImage -> ImgRendererDist [headlabel="1", taillabel="1"]; + ImgRendererDist -> StyleImgRenderer [headlabel="1", taillabel="1"]; + ImgRendererDist -> ImgRenderer [headlabel="1", taillabel="*"]; + + ImgRenderer -> ImgRendererDist [arrowhead="none", arrowtail="empty", + dir="both", style="dashed"]; + ImgRenderer -> StyleImgRenderer [arrowhead="none", arrowtail="empty", + dir="both", style="dashed"]; + ImgRenderer -> ExternalImgRenderer [arrowhead="none", arrowtail="empty", + dir="both", style="dashed"]; + ExternalImgRenderer -> ExternalWidgetImgRenderer [arrowhead="none", + arrowtail="empty", dir="both", style="dashed"]; + + Layout -> LayoutImgRenderer [headlabel="1", taillabel="0..1"]; + ExternalImgRenderer -> LayoutImgRenderer [arrowhead="none", + arrowtail="empty", dir="both", style="dashed"]; + + Widget -> WidgetImgRenderer [headlabel="1", taillabel="0..1"]; + ExternalWidgetImgRenderer -> WidgetImgRenderer [arrowhead="none", + arrowtail="empty", dir="both", style="dashed"]; + + Textblock -> Word [headlabel="1", taillabel="*"]; + Word -> WordImgRenderer [headlabel="1", taillabel="0..1"]; + Word -> SpaceImgRenderer [headlabel="1", taillabel="0..1"]; + ExternalWidgetImgRenderer -> WordImgRenderer [arrowhead="none", + arrowtail="empty", dir="both", style="dashed"]; + WordImgRenderer -> SpaceImgRenderer [arrowhead="none", arrowtail="empty", + dir="both", style="dashed"]; +} +\enddot + +
[\ref uml-legend "legend"]
+ + +Memory management +----------------- + +dw::core::style::StyleImage extends lout::signal::ObservedObject, so +that deleting this instance can be connected to code dealing with +cache clients etc. See StyleImageDeletionReceiver and how it is +attached in StyleEngine::apply ("case CSS_PROPERTY_BACKGROUND_IMAGE"). + + +Bugs and Things Needing Improvement +=================================== + +(Mostly related to image backgrounds, when not otherwise mentioned.) + +High Priority +------------- + +**Configurability, security/privacy aspects, etc.,** which are +currently available for image widgets, should be adopted. Perhaps some +more configuration options specially for background images. + + +Medium Priority +--------------- + +**Background-attachment** is not yet implemented, and will be postponed. + +**Calls to dw::core::ImgRenderer::fatal** are incomplete. As an +example, it is not called, when connecting to a server fails. (And so, +as far as I see, no cache client is started.) + + +Low Priority +------------ + +**Alpha support:** (not related to image backgrounds) currently alpha +support (and also colormap management) is done in dicache, while +dw::Image is only created with type RGB. This leads to several problems: + +- One dicache entry (representing an image related to the same URL), + which has only one background color, may refer to different images + with different background colors. +- The dicache only handles background colors, not background images. + +The solution is basicly simple: keep alpha support out of dicache; +instead implement RGBA in dw::Image. As it seems, the main problem is +alpha support in FLTK/X11. + + +Solved (Must Be Documented) +--------------------------- + +*Drawing background images row by row may become slow. As an +alternative, dw::core::ImgRenderer::finish could be used. However, +drawing row by row could become an option.* There is now +dw::core::style::drawBackgroundLineByLine, which can be changed in the +code, and is set to *false*. The old code still exists, so changing +this to *true* activates again drawing line by line. + +(For image widgets, this could also become an option: in contexts, +when image data is retrieved in a very fast way.) + +*/ diff --git a/devdoc/dw-layout-views.doc b/devdoc/dw-layout-views.doc new file mode 100644 index 00000000..d1118489 --- /dev/null +++ b/devdoc/dw-layout-views.doc @@ -0,0 +1,256 @@ +/** \page dw-layout-views Layout and Views + +Rendering of Dw is done in a way resembling the model-view pattern, at +least formally. Actually, the counterpart of the model, the layout +(dw::core::Layout), does a bit more than a typical model, namely the +layouting (delegated to the widget tree, see \ref dw-layout-widgets), +and the view does a bit less than a typical view, i.e. only the actual +drawing. + +Additionally, there is a structure representing common properties of +the platform. A platform is typically related to the underlying UI +toolkit, but other uses may be thought of. + +This design helps to archieve two important goals: + +
    +
  • Abstraction of the actual drawing, by different implementations + of dw::core::View. + +
  • It makes portability simple. +
+ + +

Viewports

+ +Although the design implies that the usage of viewports should be +fully transparent to the layout module, this cannot be fully achieved, +for the following reasons: + +
    +
  • Some features, which are used on the level of dw::core::Widget, + e.g. anchors, refer to scrolling positions. + +
  • Size hints (see \ref dw-layout-widgets) depend on the viewport + sizes, e.g. when the user changes the window size, and so also + the size of a viewport, the text within should be rewrapped. +
+ +Therefore, dw::core::Layout keeps track of the viewport size, the +viewport position, and even the thickness of the scrollbars, they are +relevant, see below for more details. +If a viewport is not used, however, the size is not defined. + +Whether a given dw::core::View implementation is a viewport or not, is +defined by the return value of dw::core::View::usesViewport. If this +method returns false, the following methods need not to be implemented +at all: + +
    +
  • dw::core::View::getHScrollbarThickness, +
  • dw::core::View::getVScrollbarThickness, +
  • dw::core::View::scrollTo, and +
  • dw::core::View::setViewportSize. +
+ +

Scrolling Positions

+ +The scrolling position is the canvas position at the upper left corner +of the viewport. Views using viewports must + +
    +
  1. change this value on request (dw::core::View::scrollTo), and +
  2. tell other changes to the layout, e.g. caused by user events + (dw::core::Layout::scrollPosChanged). +
+ +Applications of scrolling positions (anchors, test search etc.) are +handled by the layout, in a way fully transparent to the view. + +

Scrollbars

+ +A feature of the viewport size model are scrollbars. There may be a +vertical scrollbar and a horizontal scrollbar, displaying the +relationship between canvas and viewport height or width, +respectively. If they are not needed, they are hidden, to save screen +space. + +Since scrollbars decrease the usable space of a view, dw::core::Layout +must know how much space they take. The view returns, via +dw::core::View::getHScrollbarThickness and +dw::core::View::getVScrollbarThickness, how thick they will be, when +visible. + +Viewport sizes, which denote the size of the viewport widgets, include +scrollbar thicknesses. When referring to the viewport \em excluding +the scrollbars space, we will call it "usable viewport size", this is +the area, which is used to display the canvas. + +

Drawing

+ +A view must implement several drawing methods, which work on the whole +canvas. If it is necessary to convert them (e.g. into +dw::fltk::FltkViewport), this is done in a way fully transparent to +dw::core::Widget and dw::core::Layout, instead, this is done by the +view implementation. + +There exist following situations: + +
    +
  • A view gets an expose event: It will delegate this to the + layout (dw::core::Layout::draw), which will then pass it to the + widgets (dw::core::Widget::draw), with the view as a parameter. + Eventually, the widgets will call drawing methods of the view. + +
  • A widget requests a redraw: In this case, the widget will + delegate this to the layout (dw::core::Layout::queueDraw), which + delegates it to the view (dw::core::View::queueDraw). + Typically, the view will queue these requests for efficiency. + +
  • A widget requests a resize: This case is described below, in short, + dw::core::View::queueDrawTotal is called for the view. +
+ +If the draw method of a widget is implemented in a way that it may +draw outside of the widget's allocation, it should draw into a +clipping view. A clipping view is a view related to the actual +view, which guarantees that the parts drawn outside are discarded. At +the end, the clipping view is merged into the actual view. Sample +code: + +\code +void Foo::draw (dw::core::View *view, dw::core::Rectangle *area) +{ + // 1. Create a clipping view. + dw::core::View clipView = + view->getClippingView (allocation.x, allocation.y, + allocation.width, getHeight ()); + + // 2. Draw into clip_view + clipView->doSomeDrawing (...); + + // 3. Draw the children, they receive the clipping view as argument. + dw::core::Rectangle *childArea + for () { + if (child->intersects (area, &childArea)) + child->draw (clipView, childArea); + } + + // 4. Merge + view->mergeClippingView (clipView); +} +\endcode + +A drawing process is always embedded into calls of +dw::core::View::startDrawing and dw::core::View::finishDrawing. An +implementation of this may e.g. use backing pixmaps, to prevent +flickering. + + +

Sizes

+ +In the simplest case, the view does not have any influence on +the canvas size, so it is told about changes of the +canvas size by a call to dw::core::View::setCanvasSize. This happens +in the following situations: + +
    +
  • dw::core::Layout::addWidget, +
  • dw::core::Layout::removeWidget (called by dw::core::Widget::~Widget), + and +
  • dw::core::Layout::queueResize (called by + dw::core::Widget::queueResize, when a widget itself requests a size + change). +
+ +

Viewports

+ +There are two cases where the viewport size changes: + +
    +
  • As an reaction on a user event, e.g. when the user changes the + window size. In this case, the view delegates this + change to the layout, by calling + dw::core::Layout::viewportSizeChanged. + +
  • The viewport size may also depend on the visibility of UI + widgets, which depend on the world size, e.g scrollbars, + generally called "viewport markers". This is described in a separate + section. +
+ +After the creation of the layout, the viewport size is undefined. When +a view is attached to a layout, and this view can already specify +its viewport size, it may call +dw::core::Layout::viewportSizeChanged within the implementation of +dw::core::Layout::setLayout. If not, it may do this as soon as the +viewport size is known. + +Generally, the scrollbars have to be considered. If e.g. an HTML page +is rather small, it looks like this: + +\image html dw-viewport-without-scrollbar.png + +If some more data is retrieved, so that the height exceeds the +viewport size, the text has to be rewrapped, since the available width +gets smaller, due to the vertical scrollbar: + +\image html dw-viewport-with-scrollbar.png + +Notice the different line breaks. + +This means circular dependencies between these different sizes: + +
    +
  1. Whether the scrollbars are visible or not, determines the + usable space of the viewport. + +
  2. From the usable space of the viewport, the size hints for the + toplevel are calculated. + +
  3. The size hints for the toplevel widgets may have an effect on its + size, which is actually the canvas size. + +
  4. The canvas size determines the visibility of the scrollbarss. +
+ +To make an implementation simpler, we simplify the model: + +
    +
  1. For the calls to dw::core::Widget::setAscent and + dw::core::Widget::setDescent, we will always exclude the + horizontal scrollbar thickness (i.e. assume the horizontal + scrollbar is used, although the visibility is determined correctly). + +
  2. For the calls to dw::core::Widget::setWidth, we will calculate + the usable viewport width, but with the general assumption, that + the widget generally gets higher. +
+ +This results in the following rules: + +
    +
  1. Send always (when it changes) dw::core::Layout::viewportHeight + minus the maximal value of dw::core::View::getHScrollbarThickness as + argument to dw::core::Widget::setAscent, and 0 as argument to + dw::core::Widget::setDescent. + +
  2. There is a flag, dw::core::Layout::canvasHeightGreater, which is set + to false in the following cases: + +
      +
    • dw::core::Layout::addWidget, +
    • dw::core::Layout::removeWidget (called by dw::core::Widget::~Widget), + and +
    • dw::core::Layout::viewportSizeChanged. +
    + + Whenever the canvas size is calculated (dw::core::Layout::resizeIdle), + and dw::core::Layout::canvasHeightGreater is false, a test is made, + whether the widget has in the meantime grown that high, that the second + argument should be set to true (i.e. the vertical scrollbar gets visible). + As soon as and dw::core::Layout::canvasHeightGreater is true, no such test + is done anymore. +
+ +*/ diff --git a/devdoc/dw-layout-widgets.doc b/devdoc/dw-layout-widgets.doc new file mode 100644 index 00000000..e0215562 --- /dev/null +++ b/devdoc/dw-layout-widgets.doc @@ -0,0 +1,267 @@ +/** \page dw-layout-widgets Layout and Widgets + +Both, the layouting and the drawing is delegated to a tree of +widgets. A widget represents a given part of the document, e.g. a text +block, a table, or an image. Widgets may be nested, so layouting and +drawing may be delegated by one widget to its child widgets. + +Where to define the borders of a widget, whether to combine different +widgets to one, or to split one widget into multiple ones, should be +considered based on different concerns: + +
    +
  • First, there are some restrictions of Dw: + +
      +
    • The allocation (this is the space a widget allocates at + a time) of a dillo widget is always rectangular, and +
    • the allocation of a child widget must be a within the allocation + of the parent widget. +
    + +
  • Since some widgets are already rather complex, an important goal + is to keep the implementation of the widget simple. + +
  • Furthermore, the granularity should not be too fine, because of the + overhead each single widget adds. +
+ +For CSS, there will be a document tree on top of Dw, this will be +flexible enough when mapping the document structure on the widget +structure, so you should not have the document structure in mind. + +

Sizes

+ +\ref dw-widget-sizes + + +

Styles

+ +Each widget is assigned a style, see dw::core::style for more +informations. + + +

Iterators

+ +Widgets must implement dw::core::Widget::iterator. There are several +common iterators: + +
    +
  • dw::core::EmptyIterator, and +
  • dw::core::TextIterator. +
+ +Both hide the constructor, use the \em create method. + +These simple iterators only iterate through one widget, it does not +have to iterate recursively through child widgets. Instead, the type +dw::core::Content::WIDGET is returned, and the next call of +dw::core::Iterator::next will return the piece of contents \em after +(not within) this child widget. + +This makes implementation much simpler, for recursive iteration, there +is dw::core::DeepIterator. + + +

Anchors and Scrolling

+ +\todo This section is not implemented yet, after the implementation, + the documentation should be reviewed. + +Here is a description, what is to be done for a widget +implementation. How to jump to anchors, set scrolling positions +etc. is described in \ref dw-usage. + +

Anchors

+ +Anchors are position markers, which are identified by a name, which is +unique in the widget tree. The widget must care about anchors in three +different situations: + +
    +
  1. Adding an anchor is inititiated by a specific widget method, e.g. + dw::Textblock::addAnchor. Here, dw::core::Widget::addAnchor must be + called, + +
  2. Whenever the position of an anchor is changed, + dw::core::Widget::changeAnchor is called (typically, this is done + in the implementation of dw::core::Widget::sizeAllocateImpl). + +
  3. When a widget is destroyed, the anchor must be removed, by calling + dw::core::Widget::removeAnchor. +
+ +All these methods are delegated to dw::core::Layout, which manages +anchors centrally. If the anchor in question has been set to jump to, +the viewport position is automatically adjusted, see \ref +dw-usage. + + +

Drawing

+ +In two cases, a widget has to be drawn: + +
    +
  1. as a reaction on an expose event, +
  2. if the widget content has changed and it needs to be redrawn. +
+ +In both cases, drawing is done by the implementation of +dw::core::Widget::draw, which draws into the view. + + +Each view provides some primitive methods for drawing, most should be +obvious. Note that the views do not know anything about dillo +widgets, and so coordinates have to be passed as canvas coordinates. + +A widget may only draw in its own allocation. If this cannot be +achieved, a clipping view can be used, this is described in +\ref dw-layout-views. Generally, drawing should then look like: + +\code +void Foo::draw (dw::core::View *view, dw::core::Rectangle *area) +{ + // 1. Create a clipping view. + dw::core::View clipView = + view->getClippingView (allocation.x, allocation.y, + allocation.width, getHeight ()); + + // 2. Draw into clip_view + clipView->doSomeDrawing (...); + + // 3. Draw the children, they receive the clipping view as argument. + dw::core::Rectangle *childArea + for () { + if (child->intersects (area, &childArea)) + child->draw (clipView, childArea); + } + + // 4. Merge + view->mergeClippingView (clipView); +} +\endcode + +Clipping views are expensive, so they should be avoided when possible. + +The second argument to dw::core::Widget::draw is the region, which has +to be drawn. This may (but needs not) be used for optimization. + +If a widget contains child widgets, it must explicitly draw these +children (see also code example above). For this, there is the useful +method dw::core::Widget::intersects, which returns, which area of the +child must be drawn. + +

Explicit Redrawing

+ +If a widget changes its contents, so that it must be redrawn, it must +call dw::core::Widget::queueDrawArea or +dw::core::Widget::queueDraw. The first variant expects a region within +the widget, the second will cause the whole widget to be redrawn. This +will cause an asynchronous call of dw::core::Widget::draw. + +If only the size changes, a call to dw::core::Widget::queueResize is +sufficient, this will also queue a complete redraw (see \ref +dw-widget-sizes.) + + +

Mouse Events

+ +A widget may process mouse events. The view (\ref dw-layout-views) +passes mouse events to the layout, which then passes them to the +widgets. There are two kinds of mouse events: + +
    +
  • events returning bool, and +
  • events returning nothing (void). +
+ +The first group consists of: + +
    +
  • dw::core::Widget::buttonPressImpl, +
  • dw::core::Widget::buttonReleaseImpl, and +
  • dw::core::Widget::motionNotifyImpl. +
+ +For these events, a widget returns a boolean value, which denotes, +whether the widget has processed this event (true) or not (false). In +the latter case, the event is delegated according to the following +rules: + +
    +
  1. First, this event is passed to the bottom-most widget, in which + allocation the mouse position is in. +
  2. If the widget does not process this event (returning false), it is + passed to the parent, and so on. +
  3. The final result (whether \em any widget has processed this event) is + returned to the view. +
+ +The view may return this to the UI toolkit, which then interprets this +in a similar way (whether the viewport, a UI widget, has processed +this event). + +These events return nothing: + +
    +
  • dw::core::Widget::enterNotifyImpl and +
  • dw::core::Widget::leaveNotifyImpl. +
+ +since they are bound to a widget. + +When processing mouse events, the layout always deals with two +widgets: the widget, the mouse pointer was in, when the previous mouse +event was processed, (below called the "old widget") and the widget, +in which the mouse pointer is now ("new widget"). + +The following paths are calculated: + +
    +
  1. the path from the old widget to the nearest common ancestor of the old + and the new widget, and +
  2. the path from this ancestor to the new widget. +
+ +For the widgets along these paths, dw::core::Widget::enterNotifyImpl +and dw::core::Widget::leaveNotifyImpl are called. + +

Signals

+ +If a caller outside of the widget is interested in these events, he +can connect a dw::core::Layout::LinkReceiver. For those events with a +boolean return value, the results of the signal emission is regarded, +i.e. the delegation of an event to the parent of the widget can be +stopped by a signal receiver returning true, even if the widget method +returns false. + +First, the widget method is called, then (in any case) the signal is +emitted. + +

Selection

+ +If your widget has selectable contents, it should delegate the events +to dw::core::SelectionState (dw::core::Layout::selectionState). + + +

Miscellaneous

+ +

Cursors

+ +Each widget has a cursor, which is set by +dw::core::Widget::setCursor. If a cursor is assigned to a part of a +widget, this widget must process mouse events, and call +dw::core::Widget::setCursor explicitly. + +(This will change, cursors should become part of +dw::core::style::Style.) + +

Background

+ +Backgrounds are part of styles +(dw::core::style::Style::backgroundColor). If a widget assigns +background colors to parts of a widget (as dw::Table does for rows), +it must call dw::core::Widget::setBgColor for the children inside this +part. + +*/ diff --git a/devdoc/dw-line-breaking.doc b/devdoc/dw-line-breaking.doc new file mode 100644 index 00000000..14ab97c4 --- /dev/null +++ b/devdoc/dw-line-breaking.doc @@ -0,0 +1,470 @@ +/** \page dw-line-breaking Changes in Line-Breaking and Hyphenation + +
Info: +Should be incorporated into dw::Textblock.
+ +Introduction +============ + +For the implementation of hyphenation in dillo, not only a +hyphenation algorithm was implemented, but also, the line breaking was +changed to a simple optimization per line. Aside from the improvement +by this change per se, an important aspect is the introduction of +"penalties". Before this change, dillo put all words into a line which +fitted into it; now, a "badness" is calculated for a possible +breakpoint, and the best breakpoint, i. e. the breakpoint with the +smallest value for "badness", is chosen. This can be simply refined +to define "good" and "bad" breakpoints by assigning a "penalty"; the +best breakpoint is then the one with the smallest value of "badness + +penalty". Details can be found below. + +Example: Normal spaces have a penalty of 0, while hyphenation points +get a penalty of, say, 1, since hyphenation is generally considered as +a bit "ugly" and should rather be avoided. Consider a situation where +the word "dillo" could be hyphenated, with the following badnesses: + +- before "dillo": 0.6; +- between "dil-" and "lo": 0.2; +- after "dillo": 0.5. + +Since the penalty is added, the last value is the best one, so "dillo" +is put at the end of the line, without hyphenation. + +Under other circumstances (e. g. narrower lines), the values +might be different: + +- before "dillo": infinite; +- between "dil-" and "lo": 0.3; +- after "dillo": 1.5. + +In this case, even the addition of the penalty makes hyphenation the +best choice. + + +Literature +========== + +Breaking Paragraphs Into Lines +------------------------------ + +Although dillo does not (yet?) implement the algorithm TEX +uses for line breaking, this document shares much of the notation used +by the article *Breaking Paragraphs Into Lines* by Donald E. Knuth and +Michael F. Plass; originally published in: Software -- Practice and +Experience **11** (1981), 1119-1184; reprinted in: *Digital +Typography* by Donalt E. Knuth, CSLI Publications 1999. Anyway an +interesting reading. + +Hyphenation +----------- + +Dillo uses the algorithm by Frank Liang, which is described in his +doctoral dissertation found at http://www.tug.org/docs/liang/. There +is also a description in chapter H ("Hyphenation") of *The +TEXbook* by Donald E. Knuth, Addison-Wesley 1984. + +Pattern files can be found at +http://www.ctan.org/tex-archive/language/hyphenation. + + +Overview of Changes +=================== + +Starting with this change, dw/textblock.cc has been split up; anything +related to line breaking has been moved into +dw/textblock_linebreaking.cc. This will also be done for other aspects +like floats. (Better, however, would be a clean logical split.) + +An important change relates to the way that lines are added: before, +dillo would add a line as soon as a new word for this line was +added. Now, a line is added not before the *last* word of this line is +known. This has two important implications: + +- Some values in dw::Textblock::Line, which represented values + accumulated within the line, could be removed, since now, these + values can be calculated simply in a loop. +- On the other hand, this means that some words may not belong to any + line. For this reason, in some cases (e. g. in + dw::Textblock::sizeRequestImpl) dw::Textblock::showMissingLines is + called, which creates temporary lines, which must, under other + circumstances, be removed again by + dw::Textblock::removeTemporaryLines, since they have been created + based on limited information, and so possibly in a wrong way. (See + below for details.) + +When a word can be hyphenated, an instance of dw::Textblock::Word is +used for each part. Notice that soft hyphens are evaluated +immediately, but automatic hyphenation is done in a lazy way (details +below), so the number of instances may change. There are some new +attributes: only when dw::Textblock::Word::canBeHyphenated is set to +*true*, automatic hyphenation is allowed; it is set to false when soft +hyphens are used for a word, and (of course) by the automatic +hyphenation itself. Furthermore, dw::Textblock::Word::hyphenWidth +(more details in the comment there) has to be included when +calculating line widths. + +Some values should be configurable: dw::Textblock::HYPHEN_BREAK, the +penalty for hyphens. Also dw::Textblock::Word::stretchability, +dw::Textblock::Word::shrinkability, which are both set in +dw::Textblock::addSpace. + + +Criteria for Line-Breaking +========================== + +Before these changes to line breaking, a word (represented by +dw::Textblock::Word) had the following attributes related to +line-breaking: + +- the width of the word itself, represented by + dw::Textblock::Word::size; +- the width of the space following the word, represented by + dw::Textblock::Word::origSpace. + +In a more mathematical notation, the \f$i\f$th word has a width +\f$w_i\f$ and a space \f$s_i\f$. + +A break was possible, when there was a space between the two words, +and the first possible break was chosen. + +With hyphenation, the criteria are refined. Hyphenation should only be +used when otherwise line breaking results in very large spaces. We +define: + +- the badness \f$\beta\f$ of a line, which is greater the more the + spaces between the words differ from the ideal space; +- a penalty \f$p\f$ for any possible break point. + +The goal is to find those break points, where \f$\beta + p\f$ is +minimal. + +Examples for the penalty \f$p\f$: + +- 0 for normal line breaks (between words); +- \f$\infty\f$ to prevent a line break at all costs; +- \f$-\infty\f$ to force a line +- a positive, but finite, value for hyphenation points. + +So we need the following values: + +- \f$w_i\f$ (the width of the word \f$i\f$ itself); +- \f$s_i\f$ (the width of the space following the word \f$i\f$); +- the stretchability \f$y_i\f$, a value denoting how much the space + after word\f$i\f$ can be stretched (typically \f${1\over 2} s_i\f$ + for justified text; otherwise 0, since the spaces are not + stretched); +- the shrinkability \f$y_i\f$, a value denoting how much the space + after word\f$i\f$ can be shrunken (typically \f${1\over 3} s_i\f$ + for justified text; otherwise 0, since the spaces are not shrinked); +- the penalty \f$p_i\f$, if the line is broken after word \f$i\f$; +- a width \f$h_i\f$, which is added, when the line is broken after + word \f$i\f$. + +\f$h_i\f$ is the width of the hyphen, if the word \f$i\f$ is a part of +the hyphenated word (except the last part); otherwise 0. + +Let \f$l\f$ be the (ideal) width (length) of the line, which is +e. at the top given by the browser window width. Furthermore, all words +from \f$a\f$ to \f$b\f$ are added to the line. \f$a\f$ is fixed: we do +not modify the previous lines anymore; but our task is to find a +suitable \f$b\f$. + +We define: + +\f[W_a^b = \sum_{i=a}^{b} w_i + \sum_{i=a}^{b-1} s_i + h_b\f] + +\f[Y_a^b = {Y_0}_a^b + \sum_{i=a}^{b-1} y_i\f] + +\f[Z_a^b = {Z_0}_a^b + \sum_{i=a}^{b-1} z_i\f] + + +\f$W_a^b\f$ is the total width, \f$Y_a^b\f$ the total stretchability, +and \f$Z_a^b\f$ the total shrinkability. \f${Y_0}_a^b\f$ and +\f${Z_0}_a^b\f$ are the stretchability and shrinkability defined per +line, and applied at the borders; they are 0 for justified text, but +\f${Y_0}_a^b\f$ has a positive value otherwise, see below for details. + +Furthermore the *adjustment ratio* \f$r_a^b\f$: + +- in the ideal case that \f$W_a^b = l\f$: \f$r_a^b = 0\f$; +- if \f$W_a^b < l\f$: \f$r_a^b = (l - W_a^b) / Y_a^b\f$ + (\f$r_a^b < 0\f$ in this case); +- if \f$W_a^b > l\f$: \f$r_a^b = (l - W_a^b) / Z_a^b\f$ + (\f$r_a^b < 0\f$ in this case). + +The badness \f$\beta_a^b\f$ is defined as follows: + +- if \f$r_a^b\f$ is undefined or \f$r_a^b < -1\f$: \f$\beta_a^b = \infty\f$; +- otherwise: \f$\beta_a^b = |r_a^b|^3\f$ + +The goal is to find the value of \f$b\f$ where \f$\beta_a^b + p_b\f$ +is minimal. (\f$a\f$ is given, since we do not modify the previous +lines.) + +After a couple of words, it is not predictable whether this minimum +has already been reached. There are two cases where this is possible +for a given \f$b'\f$: + +- \f$\beta_{b'}^a = \infty\f$ (line gets too tight): + \f$a \le b < b'\f$, the minimum has to be searched between these two + values; +- \f$p_{b'} = -\infty\f$ (forced line break): + \f$a \le b \le b'\f$ (there may be another minimum of + \f$\beta_a^b\f$ before; note the \f$\le\f$ instead of \f$<\f$). + +This leads to a problem that the last words of a text block are not +displayed this way, since they do not fulfill these rules for being +added to a line. For this reason, there are "temporary" lines already +described above. + +(Note that the actual calculation differs from this description, since +integer arithmetic is used for performance, which make the actual +code more complicated. See dw::Textblock::BadnessAndPenalty for +details.) + +Ragged Borders +-------------- + +For other than justified text (left-, right-aligned and centered), the +spaces between the words are not shrinked or stretched (so \f$y_i\f$ +and \f$z_i\f$ are 0), but additional space is added to the left or +right border or to both. For this reason, an additional stretchability +\f${Y_0}_a^b\f$ is added (see definition above). Since this space at +the border is 0 in an ideal case (\f$W_a^b = l\f$), it cannot be +shrunken, so \f${Z_0}_a^b\f$ is 0. + +This is not equivalent to the calculation of the total stretchability +as done for justified text, since in this case, the stretchability +depends on the number of words: consider the typical case that all +spaces and stretchabilities are equal (\f$y_a = y_{a + 1} = \ldots = +y_b\f$). With \f$n\f$ words, the total strechability would be \f$n +\cdot y_a\f$, so increase with an increasing number of words +(\f$y_a\f$ is constant). This is correct for justified text, but for +other alignments, where only one space (or two, for centered text) is +changed, this would mean that a line with many narrow words is more +stretchable than a line with few wide words. + +It is obvious that left-aligned text can be handled in the same way as +right-aligned text. [... Centered text? ...] + +The default value for the stretchability is the line height without +the space between the lines (more precisely: the maximum of all word +heights). The exact value not so important when comparing different +possible values for the badness \f$\beta_a^b\f$, when \f${Y_0}_a^b\f$ +is nearly constant for different \f$b\f$ (which is the case for the +actual value), but it is important for the comparison with penalties, +which are constant. To be considered is also that for non-justified +text, hyphenation is differently (less) desirable; this effect can be +achieved by enlarging the stretchability, which will lead to a smaller +badness, and so make hyphenation less likely. The user can configure +the stretchability by changing the preference value +*stretchability_factor* (default: 1.0). + +(Comparison to TEX: Knuth and Plass describe a method for +ragged borders, which is effectively the same as described here (Knuth +1999, pp. 93--94). The value for the stretchability of the line +is slightly less, 1 em (ibid., see also p. 72 for the +definition of the units). However, this article suggests a value for +the hyphenation penalty, which is ten times larger than the value for +justified text; this would suggest a larger value for +*stretchability_factor*.) + + +Hyphens +======= + +Words (instances of dw::Textblock::Word), which are actually part of a +hyphenated word, are always drawn as a whole, not seperately. This +way, the underlying platform is able to apply kerning, ligatures, etc. + +Calculating the width of such words causes some problems, since it is +not required that the width of text "AB" is identical to the width of +"A" plus the width of "B", just for the reasons mentioned above. It +gets even a bit more complicated, since it is required that a word +part (instance of dw::Textblock::Word) has always the same length, +independent of whether hyphenation is applied or not. Furthermore, the +hyphen length is fixed for a word; for practical reasons, it is always +the width of a hyphen, in the given font. + +For calculating the widths, consider a word of four syllables: +A-B-C-D. There are 3 hyphenation points, and so 23 = 8 +possible ways of hyphenation: ABCD, ABC-D, AB-CD, AB-C-D, A-BCD, +A-BC-D, A-B-CD, A-B-C-D. (Some of them, like the last one, are only +probable for very narrow lines.) + +Let w(A), w(B), w(C), w(D) be the word widths (part of +dw::Textblock::Word::size), which have to be calculated, and l be a +shorthand for dw::core::Platform::textWidth. Without considering +this problem, the calculation would be simple: w(A) = l(A) +etc. However, it gets a bit more complicated. Since all +non-hyphenations are drawn as a whole, the following conditions can be +concluded: + +- from drawing "ABCD" (not hyphenated at all): w(A) + w(B) + w(C) + + w(D) = l(ABCD); +- from drawing "BCD", when hyphenated as "A-BCD" ("A-" is not + considered here): w(B) + w(C) + w(D) = l(BCD); +- likewise, from drawing "CD" (cases "AB-CD" and "A-B-CD"): w(C) + + w(D) = l(CD); +- finally, for the cases "ABC-D", "AB-C-D", "A-BC-D", and "A-B-C-D": + w(D) = l(D). + +So, the calculation is simple: + +- w(D) = l(D) +- w(C) = l(CD) - w(D) +- w(B) = l(BCD) - (w(C) + w(D)) +- w(A) = l(ABCD) - (w(B) + w(C) + w(D)) + +For calculation the hyphen widths, the exact conditions would be +over-determined, even when the possibility for individual hyphen +widths (instead of simply the text width of a hyphen character) would +be used. However, a simple approach of fixed hyphen widths will have +near-perfect results, so this is kept simple. + + +Automatic Hyphenation +===================== + +When soft hyphens are used, words are immediately divided into +different parts, and so different instances of +dw::Textblock::Word. Automatic hyphenation (using Liang's algorithm) +is, however, not applied always, but only when possibly needed, after +calculating a line without hyphenation: + +- When the line is tight, the last word of the line is hyphenated; + possibly this will result in a line with less parts of this word, + and so a less tight line. +- When the line is loose, and there is another word (for the next + line) available, this word is hyphenated; possibly, some parts of + this word are taken into this line, making it less loose. + +After this, the line is re-calculated. + +A problem arrises when the textblock is rewrapped, e. g. when the +user changes the window width. In this case, some new instances of +dw::Textblock::Word must be inserted into the word list, +dw::Textblock::words. This word list is implemented as an array, which +is dynamically increased; a simple approach would involve moving all +of the n elements after position i, so +n - i steps are necessary. This would not be a +problem, since O(n) steps are necessary; however, this will be +necessary again for the next hyphenated word (at the end of a +following line), and so on, so that +(n - i1) + +(n - i2) + ..., with +i1 < i2 < ..., +which results in O(n2) steps. For this reason, the word +list is managed by the class lout::misc::NotSoSimpleVector, which uses +a trick (a second array) to deal with exactly this problem. See there +for more details. + + +Tests +===== + +There are test HTML files in the test directory. Also, there is +a program testing automatic hyphenation, test/liang, which can +be easily extended. + + +Bugs and Things Needing Improvement +=================================== + +High Priority +------------- + +None. + +Medium Priority +--------------- + +None. + +Low Priority +------------ + +**Mark the end of a paragraph:** Should dw::core::Content::BREAK still +be used? Currently, this is redundant to +dw::Textblock::BadnessAndPenalty. + +Solved (Must Be Documented) +--------------------------- + +These have been solved recently and should be documented above. + +*Bugs in hyphenation:* There seem to be problems when breaking words +containing hyphens already. Example: "Abtei-Stadt", which is divided +into "Abtei-" and "Stadt", resulting possibly in +"Abtei--[new line]Stadt". See also below under +"Medium Priority", on how to deal with hyphens and dashes. + +**Solution:** See next. + +*Break hyphens and dashes:* The following rules seem to be relevant: + +- In English, an em-dash is used with no spaces around. Breaking + before and after the dash should be possible, perhaps with a + penalty > 0. (In German, an en-dash (Halbgeviert) with spaces around + is used instead.) +- After a hyphen, which is part of a compound word, a break should be + possible. As described above ("Abtei-Stadt"), this collides with + hyphenation. + +Where to implement? In the same dynamic, lazy way like hyphenation? As +part of hyphenation? + +Notice that Liang's algorithm may behave different regarding hyphens: +"Abtei-Stadt" is (using the patterns from CTAN) divided into "Abtei-" +and "Stadt", but "Nordrhein-Westfalen" is divided into "Nord", +"rhein-West", "fa", "len": the part containing the hyphen +("rhein-West") is untouched. (Sorry for the German words; if you have +got English examples, send them me.) + +**Solution for both:** This has been implemented in +dw::Textblock::addText, in a similar way to soft hyphens. Liang's +algorithm now only operates on the parts: "Abtei" and "Stadt"; +"Nordrhein" and "Westfalen". + +*Hyphens in adjacent lines:* It should be simple to assign a larger +penalty for hyphens, when the line before is already hyphenated. This +way, hyphens in adjacent lines are penalized further. + +**Solved:** There are always two penalties. Must be documented in +detail. + +*Incorrect calculation of extremes:* The minimal width of a text block +(as part of the width extremes, which are mainly used for tables) is +defined by everything between two possible breaks. A possible break +may also be a hyphenation point; however, hyphenation points are +calculated in a lazy way, when the lines are broken, and not when +extremes are calculated. So, it is a matter of chance whether the +calculation of the minimal width will take the two parts "dil-" and +"lo" into account (when "dillo" has already been hyphenated), or only +one part, "dillo" (when "dillo" has not yet been hyphenated), +resulting possibly in a different value for the minimal width. + +Possible strategies to deal with this problem: + +- Ignore. The implications should be minimal. +- Any solution will make it neccessary to hyphenate at least some + words when calculating extremes. Since the minimal widths of all + words are used to calculate the minimal width of the text block, the + simplest approach will hyphenate all words. This would, of course, + eliminate the performance gains of the current lazy approach. +- The latter approach could be optimized in some ways. Examples: (i) + If a word is already narrower than the current accumulated value for + the minimal width, it makes no sense to hyphenate it. (ii) In other + cases, heuristics may be used to estimate the number of syllables, + the width of the widest of them etc. + +**Solved:** Hyphenated parts of a word are not considered anymore for +width extremes, but only whole words. This is also one reason for the +introduction of the paragraphs list. + +**Also:** + +- Configuration of penalties. + +*/ diff --git a/devdoc/dw-map.doc b/devdoc/dw-map.doc new file mode 100644 index 00000000..aebeb7da --- /dev/null +++ b/devdoc/dw-map.doc @@ -0,0 +1,59 @@ +/** \page dw-map Dillo Widget Documentation Map + +This maps includes special documentations as well as longer comments +in the sources. Arrows denote references between the documents. + +\dot +digraph G { + rankdir=LR; + node [shape=record, fontname=Helvetica, fontsize=8]; + fontname=Helvetica; fontsize=8; + + dw_overview [label="Dillo Widget Overview", URL="\ref dw-overview"]; + dw_usage [label="Dillo Widget Usage", URL="\ref dw-usage"]; + dw_layout_views [label="Layout and Views", URL="\ref dw-layout-views"]; + dw_layout_widgets [label="Layout and Widgets", + URL="\ref dw-layout-widgets"]; + dw_widget_sizes [label="Sizes of Dillo Widgets", + URL="\ref dw-widget-sizes"]; + dw_changes [label="Changes to the GTK+-based Release Version", + URL="\ref dw-changes"]; + dw_images_and_backgrounds [label="Images and Backgrounds in Dw", + URL="\ref dw-images-and-backgrounds"]; + dw_Image [label="dw::Image", URL="\ref dw::Image"]; + dw_core_Imgbuf [label="dw::core::Imgbuf", URL="\ref dw::core::Imgbuf"]; + dw_core_SelectionState [label="dw::core::SelectionState", + URL="\ref dw::core::SelectionState"]; + dw_core_style [label="dw::core::style", URL="\ref dw::core::style"]; + dw_Table [label="dw::Table", URL="\ref dw::Table"]; + dw_Textblock [label="dw::Textblock", URL="\ref dw::Textblock"]; + dw_core_ui [label="dw::core::ui", URL="\ref dw::core::ui"]; + + dw_overview -> dw_changes; + dw_overview -> dw_usage; + dw_overview -> dw_core_style; + dw_overview -> dw_core_ui; + dw_overview -> dw_images_and_backgrounds; + dw_overview -> dw_layout_widgets; + dw_overview -> dw_widget_sizes; + dw_overview -> dw_layout_views; + + dw_usage -> dw_Table; + dw_usage -> dw_Textblock; + dw_usage -> dw_core_style; + dw_usage -> dw_core_ui; + dw_usage -> dw_images_and_backgrounds; + + dw_layout_widgets -> dw_widget_sizes; + dw_layout_widgets -> dw_core_SelectionState; + + dw_widget_sizes -> dw_Table; + dw_widget_sizes -> dw_Textblock; + + dw_images_and_backgrounds -> dw_core_Imgbuf; + dw_images_and_backgrounds -> dw_Image; + + dw_core_style -> dw_Textblock; +} +\enddot +*/ \ No newline at end of file diff --git a/devdoc/dw-out-of-flow-2.doc b/devdoc/dw-out-of-flow-2.doc new file mode 100644 index 00000000..d9d70565 --- /dev/null +++ b/devdoc/dw-out-of-flow-2.doc @@ -0,0 +1,69 @@ +/** \page dw-out-of-flow-2 Handling Elements Out Of Flow (notes 2) + +This has to be integrated into \ref dw-out-of-flow. + +Constructing a page with floats +------------------------------- +When a page is constructed (dw::Textblock::addWord), the *generating* +block tells the positions of floats (or, generally, widgets out of +flow) via dw::OutOfFlowMgr::tellPosition. This method considers +already collisions with other floats (only previous floats; floats +following this float are not considered); after the call, +dw::OutOfFlowMgr::getBorder will return correct values. + +dw::OutOfFlowMgr::tellPosition also checks for overlaps of this float +with other textblocks, except this textblock (the *generator*, which +is just constructed, so nothing has to be done). The fact that the +position of the float is the top, and so the float has only an +allocation below this position, leads to the effect that only the +textblocks following the generator are affected. (**Check:** Can the +search be limited here?) When a page is constructed, no textblocks +should be following the generating block, so no textblocks are +affected. + +**Todo:** Clarify details of line breaking (\ref dw-line-breaking). + +Float changes its size +---------------------- +The float itself will call queueResize, which will result in a call of +markSizeChange for the *containing* block, which will then call +dw::OutOfFlowMgr::markSizeChange. Here, the vloat is only *marked* as +dirty; the size will be calculated later (in +dw::OutOfFlowMgr::ensureFloatSize). + +This will trigger the resize idle function, so sizeRequest and +sizeAllocate for all floats and textblocks. In this run, +dw::OutOfFlowMgr::hasRelationChanged will return *true*, and so result +in a call of dw::Textblock::borderChanged, and trigger a second run of +the resize idle function, dealing correctly with the new size. + +(This case is handles in a not perfectly optimal way, since two runs +of the resize idle function are neccessary; but size changes of floats +is not a very common case. + +When a page is constructed (see above), a changing size of a float +currently constructed typically only affects the most bottom +textblock; the other textblocks are not covered by this float.) + +**Error:** In this case, new collisions are not yet considered. + + +Changing the width of the page +------------------------------ + +When the page width is changed, this will result in a reconstruction +of the page; see *Constructing a page with floats*. Anyway, checking +for overlaps will play a more important role. This is handled in an +optimal way by dw::OutOfFlowMgr::hasRelationChanged. + +**Check:** Are "cascades" avoided, like this: + +1. All textblocks are constructed. A float in textblock 1 overlaps + with textblock 2, so dw::Textblock::borderChanged is called for + textblock 2. +2. In another resize idle run, textblock 2 is constructed again. A + float in textblock 2 overlaps with textblock 3, so that + dw::Textblock::borderChanged is called for textblock 3. +3. Etc. + +*/ \ No newline at end of file diff --git a/devdoc/dw-out-of-flow-floats.doc b/devdoc/dw-out-of-flow-floats.doc new file mode 100644 index 00000000..53c6b220 --- /dev/null +++ b/devdoc/dw-out-of-flow-floats.doc @@ -0,0 +1,121 @@ +/** \page dw-out-of-flow-floats Handling Elements Out Of Flow: Floats + +(Note: Bases on work at , I plan +to split the documentation on elements out of flow into different +parts: general part, floats, positioned elements. In this document, +informations about floats are collected.) + + +GB lists and CB lists +===================== + +Floats generated by a block which is not yet allocated are initially +put into a list related to the *generator*: + +- dw::OutOfFlowMgr::TBInfo::leftFloatsGB or +- dw::OutOfFlowMgr::TBInfo::rightFloatsGB. + +These lists are also called GB lists. + +Floats of allocated generators are put into lists related to the +*container* (called CB lists): + +- dw::OutOfFlowMgr::leftFloatsCB or +- dw::OutOfFlowMgr::rightFloatsCB. + +As soon as the container is allocated, all floats are moved from the +GB lists to the CB lists (dw::OutOfFlowMgr::sizeAllocateStart → +dw::OutOfFlowMgr::moveFromGBToCB). + +Here, it is important to preserve the *generation order* (for reasons, +see below: *Sorting floats*), i. e. the order in which floats have +been added (dw::OutOfFlowMgr::addWidgetOOF). This may become a bit +more complicated in a case like this: + + + + + +
+
float 1
+
+
float 2
+
+
float 3
+
+ + +The floats are generated in this order: + +- \#fl-1 (generated by \#bl-1), +- \#fl-2 (generated by \#bl-2), +- \#fl-3 (generated by \#bl-1). + +Since the floats must be moved into the CB list in this order, it +becomes clear that the floats from one GB list cannot be moved at +once. For this reason, each float is assigned a "mark", which is +different from the last one as soon as the generator is *before* the +generator of the float added before. In the example above, there are +three generators: body, \#bl-1, and \#bl-2 (in this order), and floats +are assigned these marks: + +- \#fl-1: 0, +- \#fl-2: also 0, +- \#fl-3 is assigned 1, since its generator (\#bl-1) lies before the + last generator (\#bl-2). + +dw::OutOfFlowMgr::moveFromGBToCB will then iterate over all marks, so +that the generation order is preserved. + + +Sorting floats +============== + +Floats are sorted, to make binary search possible, in these lists: + +- for each generator: dw::OutOfFlowMgr::TBInfo::leftFloatsGB and + dw::OutOfFlowMgr::TBInfo::rightFloatsGB; +- for the container: dw::OutOfFlowMgr::leftFloatsCB and + dw::OutOfFlowMgr::rightFloatsCB. + +The other two lists, dw::OutOfFlowMgr::leftFloatsAll and +dw::OutOfFlowMgr::rightFloatsAll are not sorted at all. + +New floats are always added to the end of either list; this order is +called *generation order*. See also above: *GB lists and CB lists*. + +On the other hand, there are different sorting criteria, implemented +by different comparators, so that different kinds of keys may be used +for searching. These sorting criteria are equivalent to the generation +order. + +dw::OutOfFlowMgr::Float::CompareSideSpanningIndex compares +*sideSpanningIndex* (used to compare floats to those on the respective +other side); if you look at the definition +(dw::OutOfFlowMgr::addWidgetOOF) it becomes clear that this order is +equivalent to the generation order. + +dw::OutOfFlowMgr::Float::CompareGBAndExtIndex compares *externalIndex* +for floats with same generators, otherwise: (i) if one generator (T1) +is a direct anchestor of the other generator (T2), the child of T1, +which is an anchestor of, or identical to, T2 is compared to the float +generated by T1, using *externalIndex*, as in this example: + + T1 -+-> child --> ... -> T2 -> Float + `-> Float + +Otherwise, the two blocks are compared, according to their position in +dw::OutOfFlowMgr::tbInfos: + + common anchestor -+-> ... --> T1 -> Float + `-> ... --> T2 -> Float + +This is equivalent to the generation order, as long it is ensured that +*externalIndex* reflects the generation order within a generating +block, for both floats and child blocks. + +dw::OutOfFlowMgr::Float::ComparePosition ... + +*/ diff --git a/devdoc/dw-out-of-flow.doc b/devdoc/dw-out-of-flow.doc new file mode 100644 index 00000000..ea4a52bc --- /dev/null +++ b/devdoc/dw-out-of-flow.doc @@ -0,0 +1,214 @@ +/** \page dw-out-of-flow Handling Elements Out Of Flow + + +
Info: +Should be incorporated into dw::Textblock.
+ +Introduction +============ + +This texts deals with both floats and absolute positions, which have +in common that there is a distinction between generating block and +containing block (we are here using the same notation as in the +CSS 2 specification). Consider this snippet (regarding floats): + + +
    +
  • Some text.
  • +
  • +
    Some longer text, so + that the effect described in this passage can be + demonstrated. +
    + Some more and longer text.
  • +
  • Final text. Plus some more to demonstrate how text flows + around the float on the right side.
  • +
+ +which may be rendered like this + +\image html dw-floats-01.png + +The float (the DIV section, yellow in the image) is defined +("generated") within the list item (blue), so, in CSS 2 terms, the +list item is the generating block of the float. However, as the image +shows, the float is not contained by the list item, but another block, +several levels above (not shown here). In terms of ::dw, this means +that the dw::Textblock representing the float cannot be a child of the +dw::Textblock representing the generating block, the list item, since +the allocation of a child widget must be within the allocation of the +parent widget. Instead, to each dw::Textblock, another dw::Textblock +is assigned as the containing box. + +(Notice also that other text blocks must regard floats to calculate +their borders, and so their size. In this example, the following list +item (green) must consider the position of the float. This is +discussed in detail in the next section.) + +Both in this text and the code, generating and containing block are +abbreviated with **GB** and **CB**, respectively. + + +Implementation overview +======================= + +Widget level +------------ +The terms *generating block* and *containing block* have been raised +to a higher level, the one of dw::core::Widget, and are here called +*generating widget* and *containing widget*. To represent the +distinction, the type of dw::core::Content has been split into three +parts: + +- If a widget is out of flow, the generating widget keeps a reference + with the type dw::core::Content::WIDGET_OOF_REF, while the + containing block refers to it as dw::core::Content::WIDGET_OOF_CONT. +- For widgets within flow, dw::core::Content::WIDGET_IN_FLOW is used. + +Notice that in the first case, there are two pieces of content +referring to the same widget. + +An application of this distinction is iterators. [TODO: more. And +still missing: DeepIterator may need the generating parent widget in +some cases.] + + +Textblock level +--------------- +Both dw::Textblock::notifySetAsTopLevel and +dw::Textblock::notifySetParent set the member +dw::Textblock::containingBlock appropriately, (according to rules +which should be defined in this document). + +Handling widgets out of flow is partly the task of the new class +dw::OutOfFlowMgr, which is stored by dw::Textblock::outOfFlowMgr, but +only for containing blocks. Generating blocks should refer to +*containingBlock->outOfFlowMgr*. (Perhaps dw::OutOfFlowMgr may become +independent of dw::Textblock.) + +dw::Textblock::addWidget is extended, so that floats and absolutely +positioned elements can be added. Notice that not *this* widget, but +the containing block becomes the parent of the newly added child, if +it is out of flow. dw::Textblock::addWidget decides this by calling +dw::OutOfFlowMgr::isOutOfFlow. (See new content types above.) + +dw::core::Widget::parentRef has become a new representation. Before, +it represented the line numer. Now (least signifant bit left): + + +---+ - - - +---+---+- - - - - -+---+---+---+---+ + | line number | 0 | + +---+ - - - +---+---+- - - - - -+---+---+---+---+ + + +---+ - - - +---+---+- - - - - -+---+---+---+---+ + | left float index | 0 | 0 | 1 | + +---+ - - - +---+---+- - - - - -+---+---+---+---+ + + +---+ - - - +---+---+- - - - - -+---+---+---+---+ + | right float index | 1 | 0 | 1 | + +---+ - - - +---+---+- - - - - -+---+---+---+---+ + + +---+ - - - +---+---+- - - - - -+---+---+---+---+ + | absolutely positioned index | 1 | 1 | + +---+ - - - +---+---+- - - - - -+---+---+---+---+ + +Details are hidden by static inline methods of dw::OutOfFlowMgr. + + +The sizeRequest/sizeAllocate problem +======================================== + +*See also:* \ref dw-widget-sizes, especially the section *Rules for +Methods Related to Resizing*. + +The size/position model of ::dw consists mainly of the following two +steps: + +1. First, the size of the toplevel widget is calculated. Size + calculation typically depends on the sizes of the widgets, which + are calculated recursively, but not more. +2. After this, the toplevel widget is allocated at position (0, 0), + with the previosly calculated size. Each widget must allocate its + children; here, the condition for the toplevel widget (allocated + size equals requested size) is not necessary; instead, each widget + may be allocated at every size. + +Especially for floats, this model becomes a bit difficult, for reasons +described below. For the solutions, much is centralized at the level +of the containing block, which delegates most to an instance of +dw::OutOfFlowMgr (details below). + +**The size of a widget depends on the size not only of the children.** +In the example above, the last list item (green, following the +generating list item) must know the size of the the float (which is +not a child or, generally, descendant) to determine the borders, which +is done in dw::Textblock::sizeRequestImpl. + +For this, the size model has been extended (see \ref dw-widget-sizes, +section *Rules for Methods Related to Resizing*): *sizeRequest* can be +called within *sizeRequestImpl* for other widgets that children (with +some caution). Namely, dw::Textblock::sizeRequestImpl calls +dw::core::Widget::sizeRequest for the float, via +dw::OutOfFlowMgr::getBorder and dw::OutOfFlowMgr::ensureFloatSize. + +**The size of a widget depends on the allocation of another widget.** +In the example above, both list items (blue and green) must know the +position of the float widget, within dw::Textblock::sizeRequestImpl, +to calculate the borders. The position, however, is stored in the +allocation, which is typically calculated later. + +Here, two cases must be distinguished. The position of a float is +always **relative to its generating block**, so for calculating the +borders for the generating block, the allocation needs not to be +know. For other textblocks, it needs to be known, so the calculation +of the borders will ignore floats generated by other textblocks, until +all widgets are allocated. The latter will call (when neccessary) +dw::core::Widget::queueResize, so that all border calculations are +repeated. See below (*hasRelationChanged*) for details. + +Generally, this pattern (distinguishing between GB and CB) can be +found everywhere in dw::OutOfFlowMgr. + +For details see: + +- dw::OutOfFlowMgr::getLeftBorder, dw::OutOfFlowMgr::getRightBorder, + dw::OutOfFlowMgr::getBorder (called by the first two), and + especially, dw::OutOfFlowMgr::getFloatsListForTextblock (called by + the latter), where these three cases are distinguished; +- dw::OutOfFlowMgr::sizeAllocateStart, + dw::OutOfFlowMgr::sizeAllocateEnd which are called by the containing + block. + +(This could be solved in a more simple, elegant way, when +*sizeRequest* would depend on the position. This is, however, only a +vague idea, perhaps not even feasible, and for which there are no +concrete plans, certainly not in \ref dw-grows.) + + +Implementation details +====================== + +- CB and GB lists (general pattern) (see previous section) +- binary search; different search criteria, how they accord +- lastLeftTBIndex, lastRightTBIndex etc. +- limitiation of search; extIndex etc. + + +How *hasRelationChanged* works +============================== + +... + + +Integration of line breaking and floats +======================================= + +(Positioning of floats, loop, recent works.) + + +Absolute and fixed positiones +============================= + +See . + +*/ \ No newline at end of file diff --git a/devdoc/dw-overview.doc b/devdoc/dw-overview.doc new file mode 100644 index 00000000..0c4ffb53 --- /dev/null +++ b/devdoc/dw-overview.doc @@ -0,0 +1,158 @@ +/** \page dw-overview Dillo Widget Overview + +Note: If you are already familiar with the Gtk+-based version of Dw, +read \ref dw-changes. + + +The module Dw (Dillo Widget) is responsible for the low-level rendering of +all resources, e.g. images, plain text, and HTML pages (this is the +most complex type). Dw is \em not responsible for parsing HTML, or +decoding image data. Furthermore, the document tree, which is planned +for CSS, is neither a part of Dw, instead, it is a new module on top +of Dw. + +The rendering, as done by Dw, is split into two phases: + +
    +
  • the \em layouting, this means calculating the exact positions of + words, lines, etc. (in pixel position), and +
  • the \em drawing, i.e. making the result of the layouting visible + on the screen. +
+ +The result of the layouting allocates an area, which is called +\em canvas. + +

Structure

+ +The whole Dw module can be split into the following parts: + +\dot +digraph G { + node [shape=record, fontname=Helvetica, fontsize=10]; + edge [arrowhead="open", fontname=Helvetica, fontsize=10, + labelfontname=Helvetica, labelfontsize=10, + color="#404040", labelfontcolor="#000080"]; + + subgraph cluster_core { + style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + label="Platform independent core"; + + Layout [URL="\ref dw::core::Layout"]; + Platform [URL="\ref dw::core::Platform", color="#ff8080"]; + View [URL="\ref dw::core::View", color="#ff8080"]; + Widget [URL="\ref dw::core::Widget", color="#a0a0a0"]; + } + + subgraph cluster_fltk { + style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + label="FLTK specific part (as an\nexample for the platform specific\n\ +implementations)"; + + subgraph cluster_fltkcore { + style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + label="FLTK core"; + + FltkPlatform [URL="\ref dw::fltk::FltkPlatform"]; + FltkView [URL="\ref dw::fltk::FltkView", color="#ff8080"]; + } + + FltkViewport [URL="\ref dw::fltk::FltkViewport"]; + FltkPreview [URL="\ref dw::fltk::FltkPreview"]; + } + + subgraph cluster_widgets { + style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + label="Platform independent widgets"; + + Textblock [URL="\ref dw::Textblock"]; + AlignedTextblock [URL="\ref dw::AlignedTextblock", color="#a0a0a0"]; + Table [URL="\ref dw::Table"]; + Image [URL="\ref dw::Image"]; + etc1 [label="..."]; + etc2 [label="..."]; + } + + Layout -> Platform [headlabel="1", taillabel="1"]; + Layout -> View [headlabel="*", taillabel="1"]; + + Layout -> Widget [headlabel="1", taillabel="1", label="topLevel"]; + Widget -> Widget [headlabel="*", taillabel="1", label="children"]; + + Widget -> Textblock [arrowhead="none", arrowtail="empty", dir="both"]; + Widget -> Table [arrowhead="none", arrowtail="empty", dir="both"]; + Widget -> Image [arrowhead="none", arrowtail="empty", dir="both"]; + Widget -> etc1 [arrowhead="none", arrowtail="empty", dir="both"]; + Textblock -> AlignedTextblock [arrowhead="none", arrowtail="empty", + dir="both"]; + AlignedTextblock -> etc2 [arrowhead="none", arrowtail="empty", dir="both"]; + + Platform -> FltkPlatform [arrowhead="none", arrowtail="empty", dir="both", + style="dashed"]; + FltkPlatform -> FltkView [headlabel="*", taillabel="1"]; + + View -> FltkView [arrowhead="none", arrowtail="empty", dir="both"]; + FltkView -> FltkViewport [arrowhead="none", arrowtail="empty", dir="both", + style="dashed"]; + FltkView -> FltkPreview [arrowhead="none", arrowtail="empty", dir="both", + style="dashed"]; +} +\enddot + +
[\ref uml-legend "legend"]
+ +\em Platform means in most cases the underlying UI toolkit +(e.g. FLTK). A layout is bound to a specific platform, but multiple +platforms may be handled in one program. + +A short overview: + +
    +
  • dw::core::Layout is the central class, it manages the widgets and the + view, and provides delegation methods for the platform. + +
  • The layouting is done by a tree of widgets (details are described in + \ref dw-layout-widgets), also the drawing, which is finally delegated + to the view. + +
  • The view (implementation of dw::core::View) provides primitive methods + for drawing, but also have an influence on + the canvas size (via size hints). See \ref dw-layout-views for details. + +
  • Some platform dependencies are handled by implementations + of dw::core::Platform. +
+ + +

Header Files

+ +The structures mentioned above can be found in the following header +files: + +
    +
  • Anything from the Dw core in core.hh. Do not include the single files. + +
  • The single widgets can be found in the respective header files, e.g. + image.hh for dw::Image. + +
  • The core of the FLTK implementation is defined in fltkcore.hh. This + includes dw::fltk::FltkPlatform, dw::fltk::FltkView, but not the concrete + view implementations. + +
  • The views can be found in single header files, e.g fltkviewport.hh for + dw::fltk::FltkViewport. +
+ + +

Further Documentations

+ +A complete map can be found at \ref dw-map. + +
    +
  • For learning, how to use Dw, read \ref dw-usage and related documents, + dw::core::style, dw::core::ui and \ref dw-images-and-backgrounds. +
  • Advanced topics are described in \ref dw-layout-widgets, + \ref dw-widget-sizes and \ref dw-layout-views. +
+ +*/ diff --git a/devdoc/dw-size-of-widget.png b/devdoc/dw-size-of-widget.png new file mode 100644 index 00000000..dbdbe0c4 Binary files /dev/null and b/devdoc/dw-size-of-widget.png differ diff --git a/devdoc/dw-style-box-model.png b/devdoc/dw-style-box-model.png new file mode 100644 index 00000000..bf2fb1f1 Binary files /dev/null and b/devdoc/dw-style-box-model.png differ diff --git a/devdoc/dw-style-length-absolute.png b/devdoc/dw-style-length-absolute.png new file mode 100644 index 00000000..9ea28cad Binary files /dev/null and b/devdoc/dw-style-length-absolute.png differ diff --git a/devdoc/dw-style-length-percentage.png b/devdoc/dw-style-length-percentage.png new file mode 100644 index 00000000..b1ad79c9 Binary files /dev/null and b/devdoc/dw-style-length-percentage.png differ diff --git a/devdoc/dw-style-length-relative.png b/devdoc/dw-style-length-relative.png new file mode 100644 index 00000000..ee79b1a9 Binary files /dev/null and b/devdoc/dw-style-length-relative.png differ diff --git a/devdoc/dw-textblock-collapsing-spaces-1-1.png b/devdoc/dw-textblock-collapsing-spaces-1-1.png new file mode 100644 index 00000000..d528dfb2 Binary files /dev/null and b/devdoc/dw-textblock-collapsing-spaces-1-1.png differ diff --git a/devdoc/dw-textblock-collapsing-spaces-1-2.png b/devdoc/dw-textblock-collapsing-spaces-1-2.png new file mode 100644 index 00000000..483e79d1 Binary files /dev/null and b/devdoc/dw-textblock-collapsing-spaces-1-2.png differ diff --git a/devdoc/dw-textblock-collapsing-spaces-2-1.png b/devdoc/dw-textblock-collapsing-spaces-2-1.png new file mode 100644 index 00000000..0a03ea80 Binary files /dev/null and b/devdoc/dw-textblock-collapsing-spaces-2-1.png differ diff --git a/devdoc/dw-textblock-collapsing-spaces-2-2.png b/devdoc/dw-textblock-collapsing-spaces-2-2.png new file mode 100644 index 00000000..b89c6254 Binary files /dev/null and b/devdoc/dw-textblock-collapsing-spaces-2-2.png differ diff --git a/devdoc/dw-usage.doc b/devdoc/dw-usage.doc new file mode 100644 index 00000000..a23920b8 --- /dev/null +++ b/devdoc/dw-usage.doc @@ -0,0 +1,375 @@ +/** \page dw-usage Dillo Widget Usage + +This document describes the usage of Dw, without going too much into +detail. + + +

Getting Started

+ +In this section, a small runnable example is described, based on the +FLTK implementation. + +As described in \ref dw-overview, the following objects are needed: + +
    +
  • dw::core::Layout, +
  • an implementation of dw::core::Platform (we will use + dw::fltk::FltkPlatform), +
  • at least one implementation of dw::core::View (dw::fltk::FltkViewport), + and +
  • some widgets (for this example, only a simple dw::Textblock). +
+ +First of all, the necessary \#include's: + +\code +#include +#include + +#include "dw/core.hh" +#include "dw/fltkcore.hh" +#include "dw/fltkviewport.hh" +#include "dw/textblock.hh" +\endcode + +Everything is put into one function: + +\code +int main(int argc, char **argv) +{ +\endcode + +As the first object, the platform is instantiated: + +\code + dw::fltk::FltkPlatform *platform = new dw::fltk::FltkPlatform (); +\endcode + +Then, the layout is created, with the platform attached: + +\code + dw::core::Layout *layout = new dw::core::Layout (platform); +\endcode + +For the view, we first need a FLTK window: + +\code + Fl_Window *window = new Fl_Window(200, 300, "Dw Example"); + window->begin(); +\endcode + +After this, we can create a viewport, and attach it to the layout: + +\code + dw::fltk::FltkViewport *viewport = + new dw::fltk::FltkViewport (0, 0, 200, 300); + layout->attachView (viewport); +\endcode + +Each widget needs a style (dw::core::style::Style, see dw::core::style), +so we construct it here. For this, we need to fill a +dw::core::style::StyleAttrs structure with values, and call +dw::core::style::Style::create (latter is done further below): + +\code + dw::core::style::StyleAttrs styleAttrs; + styleAttrs.initValues (); + styleAttrs.margin.setVal (5); +\endcode + +dw::core::style::StyleAttrs::initValues sets several default +values. The last line sets a margin of 5 pixels. Next, we need a +font. Fonts are created in a similar way, first, the attributes are +defined: + +\code + dw::core::style::FontAttrs fontAttrs; + fontAttrs.name = "Bitstream Charter"; + fontAttrs.size = 14; + fontAttrs.weight = 400; + fontAttrs.style = dw::core::style::FONT_STYLE_NORMAL; + fontAttrs.letterSpacing = 0; + fontAttrs.fontVariant = dw::core::style::FONT_VARIANT_NORMAL; +\endcode + +Now, the font can be created: + +\code + styleAttrs.font = dw::core::style::Font::create (layout, &fontAttrs); +\endcode + +As the last attributes, the background and forground colors are +defined, here dw::core::style::Color::createSimple must be called: + +\code + styleAttrs.color = + dw::core::style::Color::create (layout, 0x000000); + styleAttrs.backgroundColor = + dw::core::style::Color::create (layout, 0xffffff); +\endcode + +Finally, the style for the widget is created: + +\code + dw::core::style::Style *widgetStyle = + dw::core::style::Style::create (layout, &styleAttrs); +\endcode + +Now, we create a widget, assign a style to it, and set it as the +toplevel widget of the layout: + +\code + dw::Textblock *textblock = new dw::Textblock (false); + textblock->setStyle (widgetStyle); + layout->setWidget (textblock); +\endcode + +The style is not needed anymore (a reference is added in +dw::core::Widget::setStyle), so it should be unreferred: + +\code + widgetStyle->unref(); +\endcode + +Now, some text should be added to the textblock. For this, we first +need another style. \em styleAttrs can still be used for this. We set +the margin to 0, and the background color to "transparent": + +\code + styleAttrs.margin.setVal (0); + styleAttrs.backgroundColor = NULL; + + dw::core::style::Style *wordStyle = + dw::core::style::Style::create (layout, &styleAttrs); +\endcode + +This loop adds some paragraphs: + +\code + for(int i = 1; i <= 10; i++) { + char buf[4]; + sprintf(buf, "%d.", i); + + char *words[] = { "This", "is", "the", buf, "paragraph.", + "Here", "comes", "some", "more", "text", + "to", "demonstrate", "word", "wrapping.", + NULL }; + + for(int j = 0; words[j]; j++) { + textblock->addText(strdup(words[j]), wordStyle); +\endcode + +Notice the \em strdup, dw::Textblock::addText will feel responsible +for the string, and free the text at the end. (This has been done to +avoid some overhead in the HTML parser.) + +The rest is simple, it also includes spaces (which also have styles): + +\code + textblock->addSpace(wordStyle); + } +\endcode + +Finally, a paragraph break is added, which is 10 pixels high: + +\code + textblock->addParbreak(10, wordStyle); + } +\endcode + +Again, this style should be unreferred: + +\code + wordStyle->unref(); +\endcode + +After adding text, this method should always be called (for faster +adding large text blocks): + +\code + textblock->flush (); +\endcode + +Some FLTK stuff to finally show the window: + +\code + window->resizable(viewport); + window->show(); + int errorCode = Fl::run(); +\endcode + +For cleaning up, it is sufficient to destroy the layout: + +\code + delete layout; +\endcode + +And the rest + +\code + return errorCode; +} +\endcode + +If you compile and start the program, you should see the following: + +\image html dw-example-screenshot.png + +Try to scroll, or to resize the window, you will see, that everything +is done automatically. + +Of course, creating new widgets, adding text to widgets etc. can also +be done while the program is running, i.e. after fltk::run has been +called, within timeouts, idles, I/O functions etc. Notice that Dw is +not thread safe, so that everything should be done within one thread. + +With the exception, that you have to call dw::Textblock::flush, +everything gets immediately visible, within reasonable times; Dw has +been optimized for frequent updates. + + +

List of all Widgets

+ +These widgets are used within dillo: + +
    +
  • dw::core::ui::Embed +
  • dw::AlignedTextblock +
  • dw::Bullet +
  • dw::Ruler +
  • dw::Image +
  • dw::ListItem +
  • dw::Table +
  • dw::TableCell +
  • dw::Textblock +
+ +If you want to create a new widget, refer to \ref dw-layout-widgets. + + +

List of Views

+ +There are three dw::core::View implementations for FLTK: + +
    +
  • dw::fltk::FltkViewport implements a viewport, which is used in the + example above. + +
  • dw::fltk::FltkPreview implements a preview window, together with + dw::fltk::FltkPreviewButton, it is possible to have a scaled down + overview of the whole canvas. + +
  • dw::fltk::FltkFlatView is a "flat" view, i.e. it does not support + scrolling. It is used for HTML buttons, see + dw::fltk::ui::FltkComplexButtonResource and especially + dw::fltk::ui::FltkComplexButtonResource::createNewWidget for details. +
+ +More informations about views in general can be found in \ref +dw-layout-views. + + +

Iterators

+ +For examining generally the contents of widgets, there are iterators +(dw::core::Iterator), created by the method +dw::core::Widget::iterator (see there for more details). + +These simple iterators only iterate through one widget, and return +child widgets as dw::core::Content::WIDGET. The next call of +dw::core::Iterator::next will return the piece of contents \em after +(not within) this child widget. + +If you want to iterate through the whole widget trees, there are two +possibilities: + +
    +
  1. Use a recursive function. Of course, with this approach, you are + limited by the program flow. + +
  2. Maintain a stack of iterators, so you can freely pass this stack + around. This is already implemented, as dw::core::DeepIterator. +
+ +As an example, dw::core::SelectionState represents the selected region +as two instances of dw::core::DeepIterator. + + +

Finding Text

+ +See dw::core::Layout::findtextState and dw::core::FindtextState +(details in the latter). There are delegation methods: + +
    +
  • dw::core::Layout::search and +
  • dw::core::Layout::resetSearch. +
+ + +

Anchors and Scrolling

+ +In some cases, it is necessary to scroll to a given position, or to +an anchor, programmatically. + +

Anchors

+ +Anchors are defined by widgets, e.g. dw::Textblock defines them, when +dw::Textblock::addAnchor is called. To jump to a specific anchor +within the current widget tree, use dw::core::Layout::setAnchor. + +This can be done immediately after assignig a toplevel widget, even +when the anchor has not yet been defined. The layout will remember the +anchor, and jump to the respective position, as soon as possible. Even +if the anchor position changes (e.g., when an anchor is moved +downwards, since some space is needed for an image in the text above), +the position is corrected. + +As soon as the user scrolls the viewport, this correction is not done +anymore. If in dillo, the user request a page with an anchor, which is +quite at the bottom of the page, he may be get interested in the text +at the beginning of the page, and so scrolling down. If then, after +the anchor has been read and added to the dw::Textblock, this anchor +would be jumped at, the user would become confused. + +The anchor is dismissed, too, when the toplevel widget is removed +again. + +\todo Currently, anchors only define vertical positions. + +

Scrolling

+ +To scroll to a given position, use the method +dw::core::Layout::scrollTo. It expects several parameters: + +
    +
  • a horizontal adjustment parameter, defined by dw::core::HPosition, +
  • a vertical adjustment parameter, defined by dw::core::VPosition, and +
  • a rectangle (\em x, \em y, \em width and \em heigh) of the region + to be adjusted. +
+ +If you just want to move the canvas coordinate (\em x, \em y) into the +upper left corner of the viewport, you can call: + +\code +dw::core::Layout *layout; +// ... +layout->scrollTo(dw::core::HPOS_LEFT, dw::core::VPOS_TOP, 0, 0, 0, 0); +\endcode + +By using dw::core::HPOS_NO_CHANGE or dw::core::VPOS_NO_CHANGE, you can +change only one dimension. dw::core::HPOS_INTO_VIEW and +dw::core::VPOS_INTO_VIEW will cause the viewport to move as much as +necessary, that the region is visible in the viewport (this is +e.g. used for finding text). + + +

Further Documentations

+ +
    +
  • dw::core::style +
  • dw::core::ui +
  • \ref dw-images-and-backgrounds +
+ +*/ diff --git a/devdoc/dw-viewport-with-scrollbar.png b/devdoc/dw-viewport-with-scrollbar.png new file mode 100644 index 00000000..7ac62de3 Binary files /dev/null and b/devdoc/dw-viewport-with-scrollbar.png differ diff --git a/devdoc/dw-viewport-without-scrollbar.png b/devdoc/dw-viewport-without-scrollbar.png new file mode 100644 index 00000000..8aa20fec Binary files /dev/null and b/devdoc/dw-viewport-without-scrollbar.png differ diff --git a/devdoc/dw-widget-sizes.doc b/devdoc/dw-widget-sizes.doc new file mode 100644 index 00000000..a82d3b99 --- /dev/null +++ b/devdoc/dw-widget-sizes.doc @@ -0,0 +1,277 @@ +/** \page dw-widget-sizes Sizes of Dillo Widgets + +
Info: +Not up to date, see \ref dw-grows.
+ +Allocation +========== + +Each widget has an \em allocation at a given time, this includes + +- the position (\em x, \em y) relative to the upper left corner of the + canvas, and +- the size (\em width, \em ascent, \em descent). + +The \em canvas is the whole area available for the widgets, in most +cases, only a part is seen in a viewport. The allocation of the +toplevel widget is exactly the allocation of the canvas, i.e. + +- the position of the toplevel widget is always (0, 0), and +- the canvas size is defined by the size of the toplevel widget. + +The size of a widget is not simply defined by the width and the +height, instead, widgets may have a base line, and so are vertically +divided into an ascender (which height is called \em ascent), and a +descender (which height is called \em descent). The total height is so +the sum of \em ascent and \em descent. + +Sizes of zero are allowed. The upper limit for the size of a widget is +defined by the limits of the C++ type \em int. + +\image html dw-size-of-widget.png Allocation of a Widget + +In the example in the image, the widget has the following allocation: + +- \em x = 50 +- \em y = 50 +- \em width = 150 +- \em ascent = 150 +- \em descent = 100 + +The current allocation of a widget is hold in +dw::core::Widget::allocation. It can be set from outside by +calling dw::core::Widget::sizeAllocate. This is a concrete method, +which will call dw::core::Widget::sizeAllocateImpl (see code of +dw::core::Widget::sizeAllocate for details). + +For trivial widgets (like dw::Bullet), +dw::core::Widget::sizeAllocateImpl does not need to be +implemented. For more complex widgets, the implementation should call +dw::core::Widget::sizeAllocate (not +dw::core::Widget::sizeAllocateImpl) on all child widgets, with +appropriate child allocations. dw::core::Widget::allocation should not +be changed here, this is already done in +dw::core::Widget::sizeAllocate. + + +Requisitions +============ + +A widget may prefer a given size for the allocation. This size, the +\em requisition, should be returned by the method +dw::core::Widget::sizeRequestImpl. In the simplest case, this is +independent of the context, e.g. for an +image. dw::Image::sizeRequestImpl returns the following size: + +- If no buffer has yet been assigned (see dw::Image for more details), + the size necessary for the alternative text is returned. If no + alternative text has been set, zero is returned. + +- If a buffer has been assigned (by dw::Image::setBuffer), the root + size is returned (i.e. the original size of the image to display). + +This is a bit simplified, dw::Image::sizeRequestImpl should also deal +with margins, borders and paddings, see dw::core::style. + +From the outside, dw::Image::sizeRequest should be called, which does +a bit of optimization. Notice that in dw::Image::sizeRequestImpl, no +optimization like lazy evaluation is necessary, this is already done +in dw::Image::sizeRequest. + +A widget, which has children, will likely call dw::Image::sizeRequest +on its children, to calculate the total requisition. + +The caller (this is either the dw::core::Layout, or the parent +widget), may, but also may not consider the requisition. Instead, a +widget must deal with any allocation. (For example, dw::Image scales +the image buffer when allocated at another size.) + + +Size Hints +========== + +
Info: +Size hints have been removed, see \ref dw-grows.
+ + +Width Extremes +============== + +dw::Table uses width extremes for fast calculation of column +widths. The structure dw::core::Extremes represents the minimal and +maximal width of a widget, as defined by: + +- the minimal width is the smallest width, at which a widget can still + display contents, and +- the maximal width is the largest width, above which increasing the + width- does not make any sense. + +Especially the latter is vaguely defined, here are some examples: + +- For those widgets, which do not depend on size hints, the minimal + and the maximal width is the inherent width (the one returned by + dw::core::Widget::sizeRequest). + +- For a textblock, the minimal width is the width of the widest + (unbreakable) word, the maximal width is the width of the total + paragraph (stretching a paragraph further would only waste space). + Actually, the implementation of dw::Textblock::getExtremesImpl is a + bit more complex. + +- dw::Table is an example, where the width extremes are calculated + from the width extremes of the children. + +Handling width extremes is similar to handling requisitions, a widget +must implement dw::core::Widget::getExtremesImpl, but a caller will +use dw::core::Widget::getExtremes. + + +Resizing +======== + +When the widget changes its size (requisition), it should call +dw::core::Widget::queueResize. The next call of +dw::core::Widget::sizeRequestImpl should then return the new +size. See dw::Image::setBuffer as an example. + +Interna are described in the code of dw::core::Widget::queueResize. + +

Incremental Resizing

+ +A widget may calculate its size based on size calculations already +done before. In this case, a widget must exactly know the reasons, why +a call of dw::core::Widget::sizeRequestImpl is necessary. To make use +of this, a widget must implement the following: + +1. There is a member dw::core::Widget::parentRef, which is totally + under control of the parent widget (and so sometimes not used at + all). It is necessary to define how parentRef is used by a specific + parent widget, and it has to be set to the correct value whenever + necessary. +2. The widget must implement dw::core::Widget::markSizeChange and + dw::core::Widget::markExtremesChange, these methods are called in + two cases: + 1. directly after dw::core::Widget::queueResize, with the + argument ref was passed to dw::core::Widget::queueResize, + and + 2. if a child widget has called dw::core::Widget::queueResize, + with the value of the parent_ref member of this child. + +This way, a widget can exactly keep track on size changes, and so +implement resizing in a faster way. A good example on how to use this +is dw::Textblock. + + +Rules for Methods Related to Resizing +===================================== + +Which method can be called, when the call of another method is not +finished? These rules are important in two circumstances: + +1. To know which method can be called, and, especially, which methods + *must not* be called, within the implementation of + *sizeRequestImpl* (called by *sizeRequest*), *markSizeChange*, and + *markExtremesChange* (the latter two are called by *queueResize*). +2. On the other hand, to make sure that the calls, which are allowed, + are handled correctly, especially in implementations of + *sizeRequestImpl*, *markSizeChange*, *markExtremesChange* + +Generally, the rules defined below are, in case of doubt, rather +strict; when changing the rules, loosening is simpler than to tighten +them, since this will make it neccessary to review old code for calls +previously allowed but now forbidden. + +Short recap: + +- *QueueResize* directly calls *markSizeChange* and + *markExtremesChanges*, and queues an idle function for the actual + resizing (dw::core::Layout::resizeIdle). (The idle function is + called some time after *queueResize* is finished.) +- The resize idle function first calls *sizeRequest*, then + *sizeAllocate*, for the toplevel widget. + +In the following table, the rules are defined in detail. "Within call +of ..." includes all methods called from the original method: the +first row (*queueResize*) defines also the rules for +*markExtremesChanges* and *markExtremesChanges*, and in the second row +(*sizeAllocate*), even *sizeRequest* has to be considered. + +
Info: +Not up to date: *queueResize* can now be called recursively (so to +speak). See code there.
+ + + + + + + + +
Within call of ... ↓ + ... is call allowed of ... ? → + queueResize + sizeAllocate + sizeRequest + getExtremes +
queueResize + No + No1 + No1 + No1 +
sizeAllocate + Yes + Only for children2 + Yes(?) + Yes(?) +
sizeRequest + Yes3 + No + Limited4 + Limited4 +
getExtremes + Yes3 + No + Limited4 + Limited4 +
1) Otherwise, since these other methods +may be call *queueResize*, the limitation that *queueResize* must not +call *queueResize* can be violated. + +2) Could perhaps be loosened as for *sizeRequest* and +*getExtremes*, but there is probably no need. + +3) Therefore the distinction between *RESIZE_QUEUED* and +*NEEDS_RESIZE*, and *EXTREMES_QUEUED* and *EXTREMES_CHANGED*, +respectively. + +4) Calls only for children are safe. In other cases, you +take a large responsibility to prevent endless recursions by +(typically indirectly) calling *sizeRequest* / *getExtremes* for +direct ancestors. +
+ +Furthermore, *sizeAllocate* can only be called within a call of +dw::core::Layout::resizeIdleId, so (if you do not touch dw::core) do +not call it outside of *sizeAllocateImpl*. The other methods can be +called outsize; e. g. *sizeRequest* is called in +dw::Textblock::addWidget. + +To avoid painful debugging, there are some tests for the cases that +one method call is strictly forbidden while another method is called. + +This could be done furthermore: + +- The tests could be refined. +- Is it possible to define exacter rules, along with a proof that no + problems (like endless recursion) can occur? + + +See also +======== + +- \ref dw-grows + +*/ diff --git a/devdoc/fltk-problems.doc b/devdoc/fltk-problems.doc new file mode 100644 index 00000000..df4f1f14 --- /dev/null +++ b/devdoc/fltk-problems.doc @@ -0,0 +1,180 @@ +/** \page fltk-problems Problems with FLTK + +

dw::fltk::FltkViewport

+ +Current problems: + +
    +
  • How should dw::fltk::FltkViewport::cancelQueueDraw be implemented? + +
  • If the value of a scrollbar is changed by the program, not the user, + the callback seems not to be called. Can this be assured? + +
  • The same for dw::fltk::FltkViewport::layout? + +
  • Also, the problems with the widgets seems to work. Also sure? + +
  • When drawing, clipping of 32 bit values is not working properly. + +
  • The item group within a selection widget (menu) should not be selectable. +
+ + +

dw::fltk::FltkPlatform

+ +
    +
  • There is the problem, that fltk::font always returns a font, the + required one, or a replacements. The latter is not wanted in all + cases, e.g. when several fonts are tested. Perhaps, this could be + solved by searching in the font list. [This was true of fltk2. + What is the state of font handling now with fltk-1.3?] + +
  • Distinction between italics and oblique would be nice + (dw::fltk::FltkFont::FltkFont). +
+ + +

dw::fltk::ui::FltkCheckButtonResource

+ +Groups of Fl_Radio_Button must be added to one Fl_Group, which is +not possible in this context. There are two alternatives: + +
    +
  1. there is a more flexible way to group radio buttons, or +
  2. radio buttons are not grouped, instead, grouping (especially + unchecking other buttons) is done by the application. +
+ +(This is mostly solved.) + +

dw::fltk::FltkImgbuf

+ +Alpha transparency should be best abstracted by FLTK itself. If not, +perhaps different implementations for different window systems could +be used. Then, it is for X necessary to use GCs with clipping masks. + + +

dw::fltk::ui::ComplexButton

+ +Unfortunately, FLTK does not provide a button with Fl_Group as parent, so +that children may be added to the button. dw::fltk::ui::ComplexButton does +exactly this, and is, in an ugly way, a modified copy of the FLTK +button. + +It would be nice, if this is merged with the standard FLTK +button. Furthermore, setting the type is strange. + +If the files do not compile, it may be useful to create a new one from +the FLTK source: + +
    +
  1. Copy Fl_Button.H from FLTK to dw/fltkcomplexbutton.hh and + src/Button.cxx to dw/fltkcomplexbutton.cc. + +
  2. In both files, rename "Button" to "ComplexButton". Automatic replacing + should work. + +
  3. Apply the changes below. +
+ +The following changes should be applied manually. + +

Changes in fltkcomplexbutton.hh

+ +First of all, the \#define's for avoiding multiple includes: + +\code +-#ifndef fltk_ComplexButton_h // fltk_Button_h formerly +-#define fltk_ComplexButton_h ++#ifndef __FLTK_COMPLEX_BUTTON_HH__ ++#define __FLTK_COMPLEX_BUTTON_HH__ +\endcode + +at the beginning and + +\code +-#endif ++#endif // __FLTK_COMPLEX_BUTTON_HH__ +\endcode + +at the end. Then, the namespace is changed: + +\code +-namespace fltk { ++namespace dw { ++namespace fltk { ++namespace ui { +\endcode + +at the beginning and + +\code +-} ++} // namespace ui ++} // namespace fltk ++} // namespace dw +\endcode + +at the end. Most important, the base class is changed: + +\code +-#include "FL/Fl_Widget.H" ++#include +\endcode + +and + +\code +-class FL_API ComplexButton : public Fl_Widget { ++class ComplexButton: public Fl_Group ++{ +\endcode + +Finally, for dw::fltk::ui::ComplexButton::default_style, there is a +namespace conflict: + +\code +- static NamedStyle* default_style; ++ static ::fltk::NamedStyle* default_style; +\endcode + +

Changes in fltkcomplexbutton.cc

+ +First, \#include's: + +\code + + #include +-#include // formerly + #include + #include ++ ++#include "fltkcomplexbutton.hh" +\endcode + +Second, namespaces: + +\code ++using namespace dw::fltk::ui; +\endcode + +Since the base class is now Fl_Group, the constructor must be changed: + +\code +-ComplexButton::ComplexButton(int x,int y,int w,int h, const char *l) : Fl_Widget(x,y,w,h,l) { ++ComplexButton::ComplexButton(int x,int y,int w,int h, const char *l) : ++ Fl_Group(x,y,w,h,l) ++{ +\endcode + +Finally, the button must draw its children (end of +dw::fltk::ui::ComplexButton::draw()): + +\code ++ ++ for (int i = children () - 1; i >= 0; i--) ++ draw_child (*child (i)); + } +\endcode + +*/ diff --git a/devdoc/index.doc b/devdoc/index.doc new file mode 100644 index 00000000..59de8cd8 --- /dev/null +++ b/devdoc/index.doc @@ -0,0 +1,48 @@ +/** \mainpage + +

Overview

+ +This is a list of documents to start with: + +
    +
  • \ref lout +
  • \ref dw-overview (map at \ref dw-map) +
+ +Currently, a document \ref fltk-problems is maintained, ideally, it +will be removed soon. + +

Historical

+ +

Replacements for GTK+ and GLib

+ +There are several classes etc., which are used for tasks formerly (in the GTK+ +version of dillo) achieved by GtkObject (in 1.2.x, this is part of Gtk+) and +GLib. For an overview on all this, take a look at \ref lout. + +GtkObject is replaced by the following: + +
    +
  • lout::object::Object is a common base class for many classes used + dillo. In the namespace lout::object, there are also some more common + classes and interfaces. + +
  • A sub class of lout::object::Object is + lout::identity::IdentifiableObject, which allows to determine the + class at run-time (equivalent to GTK_CHECK_CAST in GtkObject). + +
  • For signals, there is the namespace lout::signal. +
+ +Hash tables, linked lists etc. can be found in the lout::container namespace, +several useful macros from GLib have been implemented as inline functions +in the lout::misc namespace. + +As an alternative to the macros defined in list.h, there is also a template +class, lout::misc::SimpleVector, which does the same. + +

Changes in Dw

+ +If you have been familiar with Dw before, take a look at \ref dw-changes. + +*/ diff --git a/devdoc/lout.doc b/devdoc/lout.doc new file mode 100644 index 00000000..4e1503c6 --- /dev/null +++ b/devdoc/lout.doc @@ -0,0 +1,95 @@ +/** \page lout Lots of Useful Tools + +In the "lout" directory, there are some common base functionality for +C++. Most is described as doxygen comments, this text gives an +overview. + +

Common Base Class

+ +Many classes are derived from lout::object::Object, which defines some +general methods. See there for more information. + +For the case, that you need primitive C++ types, there are some +wrappers: + + +
C++ Type Wrapper Class +
void* lout::object::Pointer +
specific pointer lout::object::TypedPointer (template class) +
int lout::object::Integer +
const char* lout::object::ConstString +
char* lout::object::String +
+ + +

Containers

+ +In the namespace lout::container, several container classes are defined, +which all deal with instances of lout::object::Object. + +

Untyped Containers

+ +In lout::container::untyped, there are the following containers: + +
    +
  • lout::container::untyped::Vector, a dynamically increases array, +
  • lout::container::untyped::List, a linked list, +
  • lout::container::untyped::HashTable, a hash table, and +
  • lout::container::untyped::Stack, a stack. +
+ +All provide specific methods, but since they have a common base class, +lout::container::untyped::Collection, they all provide iterators, by the +method lout::container::untyped::Collection::iterator. + +

Typed Containers

+ +lout::container::typed provides wrappers for the container classes defined +in lout::container::untyped, which are more type safe, by using C++ +templates. + + +

Signals

+ +For how to connect objects at run-time (to reduce dependencies), take a +look at the lout::signal namespace. + +There is also a base class lout::signal::ObservedObject, which implements +signals for deletion. + + +

Debugging

+ +In debug.hh, there are some some useful macros for debugging messages, +see the file for mor informations. + + +

Identifying Classes at Runtime

+ +If the class of an object must be identified at runtime, +lout::identity::IdentifiableObject should be used as the base class, +see there for more details. + + +

Miscellaneous

+ +The lout::misc namespace provides several miscellaneous stuff: + +
    +
  • In some contexts, it is necessary to compare objects + (less/greater), for this, also lout::misc::Comparable must be + implemented. For example., lout::container::untyped::Vector::sort and + lout::container::typed::Vector::sort cast the elements to + lout::misc::Comparable. This can be mixed with lout::object::Object. +
  • lout::misc::SimpleVector, a simple, template based vector class + (not depending on lout::object::Object) (a variant for handling a + special case in an efficient way is lout::misc::NotSoSimpleVector), +
  • lout::misc::StringBuffer, class for fast concatenation of a large number + of strings, +
  • lout::misc::BitSet implements a bitset. +
  • useful (template) functions (lout::misc::min, lout::misc::max), and +
  • some functions useful for runtime checks (lout::misc::assert, + lout::misc::assertNotReached). +
+ +*/ diff --git a/devdoc/not-so-simple-container.png b/devdoc/not-so-simple-container.png new file mode 100644 index 00000000..0af067b5 Binary files /dev/null and b/devdoc/not-so-simple-container.png differ diff --git a/devdoc/rounding-errors.doc b/devdoc/rounding-errors.doc new file mode 100644 index 00000000..a442033e --- /dev/null +++ b/devdoc/rounding-errors.doc @@ -0,0 +1,35 @@ +/** \page rounding-errors How to Avoid Rounding Errors + +(Probably, this is a standard algorithm, so if someone knows the name, +drop me a note.) + +If something like + +\f[y_i = {x_i a \over b}\f] + +is to be calculated, and all numbers are integers, a naive +implementation would result in something, for which + +\f[\sum y_i \ne {(\sum x_i) a \over b}\f] + +because of rounding errors, due to the integer division. This can be +avoided by transforming the formula into + +\f[y_i = {(\sum_{j=0}^{j=i} x_j) a \over b} - \sum_{j=0}^{j=i-1} y_j\f] + +Of corse, when all \f$y_i\f$ are calculated in a sequence, +\f$\sum_{j=0}^{j=i} x_j\f$ and \f$\sum_{j=0}^{j=i-1} y_j\f$ can be +accumulated in the same loop. Regard this as sample: + +\code +int n, x[n], a, b; // Should all be initialized. +int y[n], cumX = 0, cumY = 0; + +for (int i = 0; i < n; i++) { + cumX += x[i] + y[i] = (cumX * a) / b - cumY; + cumY += y[i]; +} +\endcode + +*/ diff --git a/devdoc/uml-legend.doc b/devdoc/uml-legend.doc new file mode 100644 index 00000000..54004ccd --- /dev/null +++ b/devdoc/uml-legend.doc @@ -0,0 +1,195 @@ +/** \page uml-legend UML Legend + +This page describes the notation for several diagrams used in the +documentation, which is a slight variation of UML. + + +

Classes

+ +Classes are represented by boxes, containing there names: + +\dot +digraph G { + node [shape=record, fontname=Helvetica, fontsize=10]; + fontname=Helvetica; fontsize=8; + "Concrete Class"; + "Abstract Class" [color="#a0a0a0"]; + Interface [color="#ff8080"]; +} +\enddot + +(In most cases, the attributes and operations are left away, for +better readibility. Just click on it, to get to the detailed +description.) + +Of course, in C++, there are no interfaces, but here, we call a class, +which has only virtual abstract methods, and so does not provide any +functionality, an interface. + +Templates get a yellow background color: + +\dot +digraph G { + node [shape=record, fontname=Helvetica, fontsize=10, + fillcolor="#ffffc0", style="filled"]; + fontname=Helvetica; fontsize=8; + "Concrete Class Template"; + "Abstract Class Template" [color="#a0a0a0"]; + "Interface Template" [color="#ff8080"]; +} +\enddot + + +

Objects

+ +In some cases, an examle for a concrete constellation of objects is +shown. An object is represented by a box containing a name and the +class, separated by a colon. + +\dot +digraph G { + node [shape=record, fontname=Helvetica, fontsize=10]; + edge [arrowhead="open", labelfontname=Helvetica, labelfontsize=10, + color="#404040", labelfontcolor="#000080"]; + fontname=Helvetica; fontsize=10; + + "x: A" -> "y1: B"; + "x: A" -> "y2: B"; +} +\enddot + +The names (\em x, \em y, and \em z) are only meant within the context +of the diagram, there needs not to be a relation to the actual names +in the program. They should be unique within the diagram. + +Classes and objects may be mixed in one diagram. + + +

Associations

+ +\dot +digraph G { + node [shape=record, fontname=Helvetica, fontsize=10]; + edge [arrowhead="open", labelfontname=Helvetica, labelfontsize=10, + color="#404040", labelfontcolor="#000080", + fontname=Helvetica, fontsize=10, fontcolor="#000080"]; + fontname=Helvetica; fontsize=10; + A -> B [headlabel="*", taillabel="1", label="x"]; +} +\enddot + +In this example, one instance of A refers to an arbitrary number of B +instances (denoted by the "*"), and each instance of B is referred by +exactly one ("1") A. The label \em x is the name of the association, +in most cases the name of the field, e.g. A::x. + +Possible other values for the \em multiplicity: + +
    +
  • a concrete number, in most cases "1", +
  • a range, e.g. "0..1", +
  • "*", denoting an arbitrary number. +
+ + +

Implementations and Inheritance

+ +\dot +digraph G { + node [shape=record, fontname=Helvetica, fontsize=10]; + edge [arrowhead="none", dir="both", arrowtail="empty", + labelfontname=Helvetica, labelfontsize=10, color="#404040", + labelfontcolor="#000080"]; + fontname=Helvetica; fontsize=10; + A[color="#ff8080"]; + B[color="#ff8080"]; + C; + D; + A -> B; + A -> C [style="dashed"]; + C -> D; +} +\enddot + +In this example, + +
    +
  • the interface B extends the interface A, +
  • the class C implements the interface A, and +
  • the class D extends the class C. +
+ + +

Template Instantiations

+ +Template instantiations are shown as own classes/interfaces, the +instantiation by the template is shown by a yellow dashed arrow: + +\dot +digraph G { + node [shape=record, fontname=Helvetica, fontsize=10]; + edge [arrowhead="none", arrowtail="empty", dir="both", + labelfontname=Helvetica, labelfontsize=10, color="#404040", + labelfontcolor="#000080"]; + fontname=Helvetica; fontsize=10; + + A[color="#ff8080"]; + B[color="#ff8080"]; + C[color="#ff8080", fillcolor="#ffffc0", style="filled"]; + C_A[color="#ff8080", label="C \"]; + C_B[color="#ff8080", label="C \"]; + D; + + C -> C_A [arrowhead="open", arrowtail="none", style="dashed", + color="#808000"]; + C -> C_B [arrowhead="open", arrowtail="none", style="dashed", + color="#808000"]; + A -> C_A; + B -> C_B; + C_A -> D [style="dashed"]; +} +\enddot + +In this example, the interface template C uses the template argument +as super interface. + + +

Packages

+ +Packages are presented by dashed rectangles: + +\dot +digraph G { + node [shape=record, fontname=Helvetica, fontsize=10]; + edge [arrowhead="none", arrowtail="empty", dir="both", + labelfontname=Helvetica, labelfontsize=10, color="#404040", + labelfontcolor="#000080"]; + fontname=Helvetica; fontsize=10; + + subgraph cluster_1 { + style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + label="package 1"; + + A; + B [color="#a0a0a0"]; + } + + subgraph cluster_2 { + style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; + label="package 2"; + + C; + D [color="#a0a0a0"]; + E + } + + A -> C; + B -> D; + D -> E; + E -> A [arrowhead="open", arrowtail="none"]; +} +\enddot + +Packages may be nested. + +*/ \ No newline at end of file diff --git a/doc/CCCwork.txt b/doc/CCCwork.txt deleted file mode 100644 index 1ea5d20e..00000000 --- a/doc/CCCwork.txt +++ /dev/null @@ -1,153 +0,0 @@ -Last review: August 04, 2009 --jcid - - ----------------------------- -Internal working for the CCC ----------------------------- - - -HTTP protocol -------------- - - - Query: | - . - 1B --> 1B 1B --> 1B --> | -------------. - .----. .----. .----. . | -I |Capi| |http| | IO | | | - '----' '----' '----' . | - 1F <-- 1F 1F <-- 1F | V - . - | [Server] - Answer: . - - 2B --> 2B 2B --> 2B | | - .----. .----. .----. . | -II |Capi| |Dpi | | IO | | | - '----' '----' '----' . | - 2F <-- 2F 2F <-- 2F <-- | <------------' - . - | - -* a_Capi_open_url() builds both the Answer and Query chains at -once (Answer first then Query), to ensure a uniform structure -that avoids complexity (e.g. race conditions). - -* Http_get() sets a callback for the DNS hostname resolve. -Normally it comes later, but may also by issued immediately if -the hostname is cached. - -* The socket FD is passed by means of OpSend by the http module -once the remote IP is known and the socket is connected. - - - -Function calls for HTTP CCC ---------------------------- - - a_Capi_open_url - if (reload) - Capi OpStart 2B (answer) [Capi] --> [dpi] --> [IO] - Capi OpStart 1B (query) [Capi] --> [http] --> [IO] - Http_get - a_Cache_open_url - if URL_E2EReload -> prepare reload - if cached - client enqueue - delayed process queue - else - Cache_entry_add - client enqueue - - -//-> - a_Http_dns_cb - Http_connect_socket - OpSend FD, BCK - OpSend FD, FWD - Http_send_query - a_Http_make_query_str - OpSend, BCK - IO_submit - a_IOwatch_add_fd (DIO_WRITE, ...) - - - Note about 'web' structures. They're created using a_Web_new(). -The web.c module keeps a list of valid webs, so anytime you're -unsure of a weak reference to 'web', it can be checked with -a_Web_valid(web). - - - ------------- -Dpi protocol ------------- - - - Query: | - . - 1B --> 1B 1B --> 1B --> | -------------. - .----. .----. .----. . | -I |Capi| |Dpi | | IO | | | - '----' '----' '----' . | - 1F <-- 1F 1F <-- 1F | V - . - | [Server] - . - Answer (same as HTTP): | | - . | - 2B --> 2B 2B --> 2B | | - .----. .----. .----. . | -II |Capi| |Dpi | | IO | | | - '----' '----' '----' . | - 2F <-- 2F 2F <-- 2F <-- | <------------' - . - | - - -CCC Construction: - - a_Capi_open_url() calls a_Capi_dpi_send_cmd() when the URL -belongs to a dpi and it is not cached. - - a_Capi_dpi_send_cmd() builds both the Answer and Query chains -at once (Answer first then Query), in the same way as HTTP does. -Note that the answer chain is the same for both, and the query -chain only differs in the module in the middle ([http] or [dpi]). - - -Function calls for DPI CCC --------------------------- - - a_Capi_open_url - a_Capi_dpi_send_cmd - Capi OpStart 2B (answer) [Capi] --> [dpi] --> [IO] - Capi OpStart 1B (query) [Capi] --> [http] --> [IO] - a_Cache_open_url - [...] - - -Normal termination: - - When the dpi server is done, it closes the FD, and OpEnd flows -from IO to Capi (answer branch). When in Capi, capi propagates -OpEnd to the query branch. - -Abnormal termination: - - The transfer may be aborted by a_Capi_conn_abort_by_url(). The -OpAbort is not yet standardized and has an ad-hoc implementation. -One idea is to have OpAbort always propagate BCK and then FWD and -to jump into the other chain when it gets to [Capi]. - - -Debugging CCC -------------- - - A simple way to "look" inside it, is to "#define VERBOSE 1" in -chain.c, and then to follow its work with a printed copy of the -diagrams in this document. - - Each new data request generates a CCC, so if you want to debug, -it's good to refine the testcase to the minimum possible number -of connections. - diff --git a/doc/Cache.txt b/doc/Cache.txt deleted file mode 100644 index 4e885df2..00000000 --- a/doc/Cache.txt +++ /dev/null @@ -1,166 +0,0 @@ - June 2000, --Jcid - Last update: Jul 09 - - ------- - CACHE - ------- - - The cache module is the main abstraction layer between -rendering and networking. - - The capi module acts as a discriminating wrapper which either -calls the cache or the dpi routines depending on the type of -request. - - Every URL must be requested using a_Capi_open_url, which -sends the request to the cache if the data is cached, to dillo's -http module for http: URLs, and through dillo's DPI system for -other URLs. - - Here we'll document non dpi requests. - - - ---------------- - CACHE PHILOSOPHY - ---------------- - - Dillo's cache is very simple; every single resource that's -retrieved (URL) is kept in memory. NOTHING is saved to disk. -This is mainly for three reasons: - - - Dillo encourages personal privacy and it assures there'll be -no recorded tracks of the sites you visited. - - - The Network is full of intermediate transparent proxys that -serve as caches. - - - If you still want to have cached stuff, you can install an -external cache server (such as WWWOFFLE), and benefit from it. - - - --------------- - CACHE STRUCTURE - --------------- - - Currently, dillo's cache code is spread in different sources: -mainly in cache.[ch], dicache.[ch] and it uses some other -functions from mime.c and web.cc. - - Cache.c is the principal source, and it also is the one -responsible for processing cache-clients (held in a queue). -Dicache.c is the interface to the decompressed RGB representations -of currently-displayed images held in DW's imgbuf. - - mime.c and web.cc are used for secondary tasks such as -assigning the right "viewer" or "decoder" for a given URL. - - ----------------- -A bit of history ----------------- - - Some time ago, the cache functions, URL retrieval and -external protocols were a whole mess of mixed code, and it was -getting REALLY hard to fix, improve or extend the functionality. -The main idea of this "layering" is to make code-portions as -independent as possible so they can be understood, fixed, -improved or replaced without affecting the rest of the browser. - - An interesting part of the process is that, as resources are -retrieved, the client (dillo in this case) doesn't know the -Content-Type of the resource at request-time. It only becomes known -when the resource header is retrieved (think of http). This -happens when the cache has control, so the cache sets the -proper viewer for it (unless the Callback function was already -specified with the URL request). - - You'll find a good example in http.c. - - Note: All resources received by the cache have HTTP-style headers. - The file/data/ftp DPIs generate these headers when sending their - non-HTTP resources. Most importantly, a Content-Type header is - generated based on file extension or file contents. - - -------------- -Cache clients -------------- - - Cache clients MUST use a_Capi_open_url to request an URL. The -client structure and the callback-function prototype are defined, -in cache.h, as follows: - -struct _CacheClient { - int Key; /* Primary Key for this client */ - const DilloUrl *Url; /* Pointer to a cache entry Url */ - int Version; /* Dicache version of this Url (0 if not used) */ - void *Buf; /* Pointer to cache-data */ - uint_t BufSize; /* Valid size of cache-data */ - CA_Callback_t Callback; /* Client function */ - void *CbData; /* Client function data */ - void *Web; /* Pointer to the Web structure of our client */ -}; - -typedef void (*CA_Callback_t)(int Op, CacheClient_t *Client); - - - Notes: - - * Op is the operation that the callback is asked to perform - by the cache. { CA_Send | CA_Close | CA_Abort }. - - * Client: The Client structure that originated the request. - - - --------------------------- -Key-functions descriptions --------------------------- - -································································ -int a_Cache_open_url(void *Web, CA_Callback_t Call, void *CbData) - - if Web->url is not cached - Create a cache-entry for that URL - Send client to cache queue - else - Feed our client with cached data - -································································ - ----------------------- -Redirections mechanism - (HTTP 30x answers) ----------------------- - - This is by no means complete. It's a work in progress. - - Whenever an URL is served under an HTTP 30x header, its cache -entry is flagged with 'CA_Redirect'. If it's a 301 answer, the -additional 'CA_ForceRedirect' flag is also set, if it's a 302 -answer, 'CA_TempRedirect' is also set (this happens inside the -Cache_parse_header() function). - - Later on, in Cache_process_queue(), when the entry is flagged -with 'CA_Redirect' Cache_redirect() is called. - - - - - - - ------------ -Notes ------------ - - The whole process is asynchronous and very complex. I'll try -to document it in more detail later (source is commented). - Currently I have a drawing to understand it; hope the ASCII -translation serves the same as the original. - If you're planning to understand the cache process thoroughly, -write me a note and I will assign higher priority to further -improvement of this doc. - Hope this helps! - - diff --git a/doc/Dillo.txt b/doc/Dillo.txt deleted file mode 100644 index a63c9588..00000000 --- a/doc/Dillo.txt +++ /dev/null @@ -1,96 +0,0 @@ -"Eliminate the guesswork and quality goes up." - - - ------- - DILLO - ------- - - These notes are written with a view to make it less hard, not -easier yet ;), to get into Dillo development. - When I first got into it, I was totally unaware of the browser -internals. Now that I've made my way deep into the core of it, -(we rewrote it 90% and modified the rest), is time to write some -documentation, just to make a less steep learning curve for new -developers. - - --Jcid - - - - -------- - OVERVIEW - -------- - - Dillo can be viewed as the sum of five main parts: - - 1.- Dillo Widget: A custom widget, FLTK-based, that holds the -necessary data structures and mechanisms for graphical rendering. -(Described in Dw*.txt, dw*.c files among the sources.) - - 2.- Dillo Cache: Integrated with a signal driven Input/Output -engine that handles file descriptor activity, the cache acts as -the main abstraction layer between rendering and networking. - Every URL, whether cached or not, must be retrieved using -a_Capi_open_url (Described briefly in Cache.txt, source -contained in capi.c). - IO is described in IO.txt (recommended), source in src/IO/. - - 3.- The HTML parser: A streamed parser that joins the Dillo -Widget and the Cache functionality to make browsing possible -(Described in HtmlParser.txt, source mainly inside html.cc). - - 4.- Image processing code: The part that handles image -retrieval, decoding, caching and displaying. (Described in -Images.txt. Sources: image.c, dw/image.cc, dicache.c, gif.c, -jpeg.c and png.c) - - 5.- The dpi framework: a gateway to interface the browser with -external programs (Example: the bookmarks server plugin). -Dpi spec: http://www.dillo.org/dpi1.html - - - ------------------------- - HOW IS THE PAGE RENDERED? - ------------------------- - -(A short description of the internal function calling process) - - When the user requests a new URL, a_UIcmd_open_url -is queried to do the job; it calls a_Nav_push (The highest level -URL dispatcher); a_Nav_push updates current browsing history and -calls Nav_open_url. Nav_open_url closes all open connections by -calling a_Bw_stop_clients, and then calls -a_Capi_open_url which calls a_Cache_open_url (or the dpi module if -this gateway is used). - - If Cache_entry_search hits (due to a cached url :), the client is -fed with cached data, but if the URL isn't cached yet, a new CCC -(Concomitant Control Chain) is created and committed to fetch the -URL. - - The next CCC link is dynamically assigned by examining the -URL's protocol. It can be a_Http_ccc or a_Dpi_ccc. - - If we have an HTTP URL, a_Http_ccc will succeed, and the http -module will be linked; it will create the proper HTTP query and -link the IO module to submit and deliver the answer. - - Note that as the Content-Type of the URL is not always known -in advance, the answering branch decides where to dispatch it to -upon HTTP header arrival. - - - What happens then? - - Well, the html parser gets fed, and proper functions are -called for each tag (to parse and call the appropriate methods) -and the whole page is contructed in a streamed way. - Somewhere in the middle of it, resize and repaint functions -are activated and idle functions draw to screen what has been -processed. - - (The process for images is described in Images.txt) - - - - diff --git a/doc/Dpid.txt b/doc/Dpid.txt deleted file mode 100644 index 6c418f57..00000000 --- a/doc/Dpid.txt +++ /dev/null @@ -1,331 +0,0 @@ -Aug 2003, Jorge Arellano Cid, - Ferdi Franceschini -- -Last update: Nov 2009 - - - ------ - dpid - ------ - -------------- -Nomenclature: -------------- - - dpi: - generic term referring to dillo's plugin system (version1). - - dpi1: - specific term for dillo's plugin spec version 1. - at: http://www.dillo.org/dpi1.html - - dpi program: - any plugin program itself. - - dpi framework: - the code base inside and outside dillo that makes dpi1 - working possible (it doesn't include dpi programs). - - dpip: - dillo plugin protocol. The HTML/XML like set of command tags - and information that goes inside the communication sockets. - Note: not yet fully defined, but functional. - Note2: it was designed to be extensible. - - dpid: - dillo plugin daemon. - - server plugin: - A plugin that is capable of accepting connections on a socket. Dpid will - never run more than one instance of a server plugin at a time. - - filter plugin: - A dpi program that reads from stdin and writes to stdout, and that - exits after its task is done (they don't remain as server plugins). - Warning, dpid will run multiple instances of filter plugins if requested. - ------------ -About dpid: ------------ - - * dpid is a program which manages dpi connections. - * dpid is a daemon that serves dillo using IDS sockets. - * dpid launches dpi programs and arranges socket communication - between the dpi program and dillo. - - The concept and motivation is similar to that of inetd. The -plugin manager (dpid) listens for a service request on a socket -and returns the socket/port pair of a plugin that handles the -service. It also watches sockets of inactive plugins and starts -them when a connection is requested. - - ------------------------------------------------------------ -What's the problem with managing dpi programs inside dillo? ------------------------------------------------------------ - - That's the other way to handle it, but it started to show some -problems (briefly outlined here): - - * When having two or more running instances of Dillo, one - should prevail, and take control of dpi managing (but all - dillos carry the managing code). - * If the managing-dillo exits, it must pass control to another - instance, or leave it void if there's no other dillo running! - * The need to synchronize all the running instances of - dillo arises. - * If the controlling instance finishes and quits, all the - dpi-program PIDs are lost. - * Terminating hanged dpis is hard if it's not done with signals - (PIDs) - * Forks can be expensive (Dillo had to fork its dpis). - * When a managing dillo exits, the new one is no longer the - parent of the forked dpis. - * If Unix domain sockets for the dpis were to be named - randomly, it gets very hard to recover their names if the - controlling instance of dillo exits and another must "take - over" the managing. - * It increments dillo's core size. - * If dillo hangs/crashes, dpi activity is lost (e.g. downloads) - * ... - - That's why the managing daemon scheme was chosen. - - ----------------------- -What does dpid handle? ----------------------- - - It solves all the above mentioned shortcomings and also can do: - - * Multiple dillos: - dpid can communicate and serve more than one instance - of dillo. - - * Multiple dillo windows: - two or more windows of the same dillo instance accessing dpis - at the same time. - - * Different implementations of the same service - dpi programs ("dpis") are just an implementation of a - service. There's no problem in implementing a different one - for the same service (e.g. downloads). - - * Upgrading a service: - to a new version or implementation without requiring - patching dillo's core or even bringing down the dpid. - - - And finally, being aware that this design can support the -following functions is very helpful: - - SCHEME Example - ------------------------------------------------------------ - * "one demand/one response" man, preferences, ... - * "resident while working" downloads, mp3, ... - * "resident until exit request" bookmarks, ... - - * "one client only" cd burner, ... - * "one client per instance" man, ... - * "multiple clients/one instance" downloads, cookies ... - - --------- -Features --------- - * Dpi programs go in: "EPREFIX/dillo/dpi" or "~/.dillo/dpi". The binaries - are named .dpi as "bookmarks.dpi" and .filter.dpi as in - "hello.filter.dpi". The ".filter" plugins simply read from stdin - and write to stdout. - * Register/update/remove dpis from list of available dpis when a - 'register_all' command is received. - * dpid terminates when it receives a 'DpiBye' command. - * dpis can be terminated with a 'DpiBye' command. - * dpidc control program for dpid, currently allows register and stop. - - ------ -todo: ------ - - These features are already designed, waiting for implementation: - - * dpidc remove // May be not necessary after all... - - ------------------ -How does it work? ------------------ - -o on startup dpid reads dpidrc for the path to the dpi directory - (usually EPREFIX/lib/dillo/dpi). ~/.dillo/dpi is scanned first. - -o both directories are scanned for the list of available plugins. - ~/.dillo/dpi overrides system-wide dpis. - -o next it creates internet domain sockets for the available plugins and - then listens for service requests on its own socket, - and for connections to the sockets of inactive plugins. - -o dpid returns the port of a plugin's socket when a client (dillo) - requests a service. - -o if the requested plugin is a 'server' then - 1) dpid stops watching the socket for activity - 2) forks and starts the plugin - 3) resumes watching the socket when the plugin exits - -o if the requested plugin is a 'filter' then - 1) dpid accepts the connection - 2) maps the socket fd to stdin/stdout (with dup2) - 3) forks and starts the plugin - 4) continues to watch the socket for new connections - - - - ---------------------------- -dpi service process diagram ---------------------------- - - These drawings should be worth a thousand words! :) - - -(I) - .--- s1 s2 s3 ... sn - | - [dpid] [dillo] - | - '--- srs - - The dpid is running listening on several sockets. - - -(II) - .--- s1 s2 s3 ... sn - | - [dpid] [dillo] - | | - '--- srs ------------------' - - dillo needs a service so it connects to the service request - socket of the dpid (srs) and asks for the socket name of the - required plugin (using dpip). - - -(III) - .--- s1 s2 s3 ... sn - | | - [dpid] | [dillo] - | | | - '--- srs '---------------' - - then it connects to that socket (s3, still serviced by dpid!) - - -(IV) - .--- s1 s2 s3 ... sn - | | - .[dpid] | [dillo] - . | | | - . '--- srs '---------------' - . - .............[dpi program] - - when s3 has activity (incoming data), dpid forks the dpi - program for it... - - -(V) - .--- s1 s2 (s3) ... sn - | - [dpid] [dillo] - | | - '--- srs .---------------' - | - [dpi program] - - ... and lets it "to take over" the socket. - - Once there's a socket channel for dpi and dillo, the whole -communication process takes place until the task is done. When -the dpi program exits, dpid resumes listening on the socket (s3). - - --------------------------------- -So, how do I make my own plugin? --------------------------------- - - Maybe the simplest way to get started is to understand a few -concepts and then to use the hands-on method by using/modifying -the hello dpi. It's designed as an example to get developers -started. - - --------- - Concepts: - --------- - - * Dillo plugins work by communicating two processes: dillo - and the dpi. - * The underlying protocol (DPIP) has a uniform API which is - powerful enough for both blocking and nonblocking IO, and - filter or server dpis. - * The simplest example is one-request one-answer (for example - dillo asks for a URL and the dpi sends it). You'll find - this and more complex examples in hello.c - - First, you should get familiar with the hello dpi as a user: - - $dillo dpi:/hello/ - - Once you've played enough with it, start reading the well -commented code in hello.c and start making changes! - - - --------------- - Debugging a dpi - --------------- - - The simplest way is to add printf-like feedback using the MSG* -macros. You can start the dpid by hand on a terminal to force -messages to go there. Filter dpis use sdterr and server dpis -stdout. - - Sometimes more complex dpis need more than MSG*. In this case -you can use gdb like this. - - 1.- Add an sleep(20) statement just after the dpi starts. - 2.- Start dillo and issue a request for your dpi. This will - get your dpi started. - 3.- Standing in the dpi source directory: - ps aux|grep dpi - 4.- Take note of the dpi's PID and start gdb, then: - (gdb) attach - 5.- Continue from there... - - - ------------ - Final Notes: - ------------ - - 1.- If you already understand the hello dpi and want to try -something more advanced: - - * bookmarks.c is a good example of a blocking server - * file.c is an advanced example of a server handling multiple - non-blocking connections with select(). - - 2.- Multiple instances of a filter plugin may be run -concurrently, this could be a problem if your plugin records data -in a file, however it is safe if you simply write to stdout. -Alternatively you could write a 'server' plugin instead as they -are guaranteed not to run concurrently. - - 3.- The hardest part is to try to modify the dpi framework code -inside dillo; you have been warned! It already supports a lot of -functionality, but if you need to do some very custom stuff, try -extending the "chat" command, or asking in dillo-dev. - - - - >>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<< - diff --git a/doc/Dw.txt b/doc/Dw.txt deleted file mode 100644 index f6909380..00000000 --- a/doc/Dw.txt +++ /dev/null @@ -1,11 +0,0 @@ -Last update: Oct 2008 - -================ -Dw: Dillo Widget -================ - -Dw is the internal widget library for rendering HTML. It has excellent -documentation. - - Just run "doxygen" and browse the html/ directory! - diff --git a/doc/HtmlParser.txt b/doc/HtmlParser.txt deleted file mode 100644 index 2eb8be63..00000000 --- a/doc/HtmlParser.txt +++ /dev/null @@ -1,124 +0,0 @@ - October 2001, --Jcid - Last update: Jul 2009 - - --------------- - THE HTML PARSER - --------------- - - - Dillo's parser is more than just a HTML parser, it does XHTML -and plain text also. It has parsing 'modes' that define its -behaviour while working: - - typedef enum { - DILLO_HTML_PARSE_MODE_INIT = 0, - DILLO_HTML_PARSE_MODE_STASH, - DILLO_HTML_PARSE_MODE_STASH_AND_BODY, - DILLO_HTML_PARSE_MODE_BODY, - DILLO_HTML_PARSE_MODE_VERBATIM, - DILLO_HTML_PARSE_MODE_PRE - } DilloHtmlParseMode; - - - The parser works upon a token-grained basis, i.e., the data -stream is parsed into tokens and the parser is fed with them. The -process is simple: whenever the cache has new data, it is -passed to Html_write, which groups data into tokens and calls the -appropriate functions for the token type (tag, space, or word). - - Note: when in DILLO_HTML_PARSE_MODE_VERBATIM, the parser -doesn't try to split the data stream into tokens anymore; it -simply collects until the closing tag. - ------- -TOKENS ------- - - * A chunk of WHITE SPACE --> Html_process_space - - - * TAG --> Html_process_tag - - The tag-start is defined by two adjacent characters: - - first : '<' - second: ALPHA | '/' | '!' | '?' - - Note: comments are discarded ( ) - - - The tag's end is not as easy to find, nor to deal with!: - - 1) The HTML 4.01 sec. 3.2.2 states that "Attribute/value - pairs appear before the final '>' of an element's start tag", - but it doesn't define how to discriminate the "final" '>'. - - 2) '<' and '>' should be escaped as '<' and '>' inside - attribute values. - - 3) The XML SPEC for XHTML states: - AttrValue ::== '"' ([^<&"] | Reference)* '"' | - "'" ([^<&'] | Reference)* "'" - - Current parser honors the XML SPEC. - - As it's a common mistake for human authors to mistype or - forget one of the quote marks of an attribute value; the - parser solves the problem with a look-ahead technique - (otherwise the parser could skip significant amounts of - properly-written HTML). - - - - * WORD --> Html_process_word - - A word is anything that doesn't start with SPACE, that's - outside of a tag, up to the first SPACE or tag start. - - SPACE = ' ' | \n | \r | \t | \f | \v - - ------------------ -THE PARSING STACK ------------------ - - The parsing state of the document is kept in a stack: - - class DilloHtml { - [...] - lout::misc::SimpleVector *stack; - [...] - }; - - struct _DilloHtmlState { - CssPropertyList *table_cell_props; - DilloHtmlParseMode parse_mode; - DilloHtmlTableMode table_mode; - bool cell_text_align_set; - DilloHtmlListMode list_type; - int list_number; - - /* TagInfo index for the tag that's being processed */ - int tag_idx; - - dw::core::Widget *textblock, *table; - - /* This is used to align list items (especially in enumerated lists) */ - dw::core::Widget *ref_list_item; - - /* This is used for list items etc; if it is set to TRUE, breaks - have to be "handed over" (see Html_add_indented and - Html_eventually_pop_dw). */ - bool hand_over_break; - }; - - Basically, when a TAG is processed, a new state is pushed into -the 'stack' and its 'style' is set to reflect the desired -appearance (details in DwStyle.txt). - - That way, when a word is processed later (added to the Dw), all -the information is within the top state. - - Closing TAGs just pop the stack. - - diff --git a/doc/IO.txt b/doc/IO.txt deleted file mode 100644 index cd62a4f5..00000000 --- a/doc/IO.txt +++ /dev/null @@ -1,468 +0,0 @@ - -This is the updated base of a paper I wrote with Horst. -It provides a good introduction to Dillo's internals. -(Highly recommended if you plan to patch or develop in Dillo) - -It may not be exactly accurate (it's quite old), but it explains -the theory behind in some detail, so it's more than recommended -reading material. ---Jcid - - ------------------------------------------------------ -Parallel network programming of the Dillo web browser ------------------------------------------------------ - - Jorge Arellano-Cid - Horst H. von Brand - - --------- -Abstract --------- - - Network programs face several delay sources when sending or -retrieving data. This is particularly problematic in programs -which interact directly with the user, most notably web browsers. -We present a hybrid approach using threads communicated through -pipes and signal driven I/O, which allows a non-blocking main -thread and overlapping waiting times. - - ------------- -Introduction ------------- - - The Dillo project didn't start from scratch but mainly working -on the code base of gzilla (a light web browser written by Raph -Levien). As the project went by, the code of the whole source was -standardized, and the networking engine was replaced with a new, -faster design. Later, the parser was changed, the streamed -handling of data and its error control was put under the control -of the CCC (Concomitant Control Chain), and the old widget system -was replaced with a new one (Dw). The source code is currently -regarded as "very stable beta", and is available at -. Dillo is a project licensed under the GNU -General Public License. - - This paper covers basic design aspects of the hybrid approach -that the Dillo web browser uses to solve several latency -problems. After introducing the main delay-sources, the main -points of the hybrid design will be addressed. - - -------------- -Delay sources -------------- - - Network programs face several delay-sources while sending or -retrieving data. In the particular case of a web browser, they -are found in: - - DNS querying: - The time required to solve a name. - - Initiating the TCP connection: - The three way handshake of the TCP protocol. - - Sending the query: - The time spent uploading queries to the remote server. - - Retrieving data: - The time spent expecting and receiving the query answer. - - Closing the TCP connection: - The four packet-sending closing sequence of the TCP protocol. - - In a WAN context, every single item of this list has an -associated delay that is non deterministic and often measured in -seconds. If we add several connections per browsed page (each one -requiring at least the 4 last steps), the total latency can be -considerable. - - ------------------------------------ -The traditional (blocking) approach ------------------------------------ - - The main problems with the blocking approach are: - - When issuing an operation that can't be completed - immediately, the process is put to sleep waiting for - completion, and the program doesn't do any other - processing in the meantime. - - When waiting for a specific socket operation to complete, - packets that belong to other connections may be arriving, - and have to wait for service. - - Web browsers handle many small transactions, - if waiting times are not overlapped - the latency perceived by the user can be very annoying. - - If the user interface is just put to sleep during network - operations, the program becomes unresponsive, confusing - and perhaps alarming the user. - - Not overlapping waiting times and processing makes - graphical rendering (which is arguably the central function - of a browser) unnecessarily slow. - - ---------------------- -Dillo's hybrid design ---------------------- - - Dillo uses threads and signal driven I/O extensively to -overlap waiting times and computation. Handling the user -interface in a thread that never blocks gives a good interactive -``feel.'' The use of GTK+, a sophisticated widget framework for -graphical user interfaces, helped very much to accomplish this -goal. All the interface, rendering and I/O engine was built upon -its facilities. - - The design is said to be ``hybrid'' because it uses threads -for DNS querying and reading local files, and signal driven I/O -for TCP connections. The threaded DNS scheme is potentially -concurrent (this depends on underlying hardware), while the I/O -handling (both local files and remote connections) is -definitively parallel. - - To simplify the structure of the browser, local files are -encapsulated into HTTP streams and presented to the rest of the -browser as such, in exactly the same way a remote connection is -handled. To create this illusion, a thread is launched. This -thread opens a pipe to the browser, it then synthesizes an -appropriate HTTP header, sends it together with the file to the -browser proper. In this way, all the browser sees is a handle, -the data on it can come from a remote connection or from a local -file. - - To handle a remote connection is more complex. In this case, -the browser asks the cache manager for the URL. The name in the -URL has to be resolved through the DNS engine, a socket TCP -connection must be established, the HTTP request has to be sent, -and finally the result retrieved. Each of the steps mentioned -could give rise to errors, which have to be handled and somehow -communicated to the rest of the program. For performance reasons, -it is critical that responses are cached locally, so the remote -connection doesn't directly hand over the data to the browser; -the response is passed to the cache manager which then relays it -to the rest of the browser. The DNS engine caches DNS responses, -and either answers them from the cache or by querying the DNS. -Querying is done in a separate thread, so that the rest of the -browser isn't blocked by long waits here. - - The activities mentioned do not happen strictly in the order -stated above. It is even possible that several URLs are being -handled at the same time, in order to overlap waiting and -downloading. The functions called directly from the user -interface have to return quickly to maintain interactive -response. Sometimes they return connection handlers that haven't -been completely set up yet. As stated, I/O is signal-driven, when -one of the descriptors is ready for data transfer (reading or -writing), it wakes up the I/O engine. - - Data transfer between threads inside the browser is handled by -pipes, shared memory is little used. This almost obviates the -need for explicit synchronization, which is one of the main areas -of complexity and bugs in concurrent programs. Dillo handles its -threads in a way that its developers can think of it as running -on a single thread of control. This is accomplished by making the -DNS engine call-backs happen within the main thread, and by -isolating file loading with pipes. - - Using threads in this way has three big advantages: - - The browser doesn't block when one of its child threads - blocks. In particular, the user interface is responsive - even while resolving a name or downloading a file. - - Developers don't need to deal with complex concurrent - concerns. Concurrency is hard to handle, and few developers - are adept at this. This gives access a much larger pool of - potential developers, something which can be critical - in an open-source development project. - - By making the code mostly sequential, debugging the code - with traditional tools like gdb is possible. Debugging - parallel programs is very hard, and appropriate tools are - hard to come by. - - Because of simplicity and portability concerns, DNS querying -is done in a separate thread. The standard C library doesn't -provide a function for making DNS queries that don't block. The -alternative is to implement a new, custom DNS querying function -that doesn't block. This is certainly a complex task, integrating -this mechanism into the thread structure of the program is much -simpler. - - Using a thread and a pipe to read a local file adds a -buffering step to the process (and a certain latency), but it has -a couple of significative advantages: - - By handling local files in the same way as remote - connections, a significant amount of code is reused. - - A preprocessing step of the file data can be added easily, - if needed. In fact, the file is encapsulated into an HTTP - data stream. - - ------------ -DNS queries ------------ - - Dillo handles DNS queries with threads, letting a child thread -wait until the DNS server answers the request. When the answer -arrives, a call-back function is called, and the program resumes -what it was doing at DNS-request time. The interesting thing is -that the call-back happens in the main thread, while the child -thread simply exits when done. This is implemented through a -server-channel design. - - -The server channel ------------------- - - There is one thread for each channel, and each channel can -have multiple clients. When the program requests an IP address, -the server first looks for a cached match; if it hits, the client -call-back is invoked immediately, but if not, the client is put -into a queue, a thread is spawned to query the DNS, and a GTK+ -idle client is set to poll the channel 5~times per second for -completion, and when it finally succeeds, every client of that -channel is serviced. - - This scheme allows all the further processing to continue on -the same thread it began: the main thread. - - ------------------------- -Handling TCP connections ------------------------- - - Establishing a TCP connection requires the well known -three-way handshake packet-sending sequence. Depending on network -traffic and several other issues, significant delay can occur at -this phase. - - Dillo handles the connection by a non blocking socket scheme. -Basically, a socket file descriptor of AF_INET type is requested -and set to non-blocking I/O. When the DNS server has resolved the -name, the socket connection process begins by calling connect(2); - {We use the Unix convention of identifying the manual section - where the concept is described, in this case - section 2 (system calls).} -which returns immediately with an EINPROGRESS error. - - After the connection reaches the EINPROGRESS ``state,'' the -socket waits in background until connection succeeds (or fails), -when that happens, a callback function is awaked to perform the -following steps: set the I/O engine to send the query and expect -its answer (both in background). - - The advantage of this scheme is that every required step is -quickly done without blocking the browser. Finally, the socket -will generate a signal whenever I/O is possible. - - ----------------- -Handling queries ----------------- - - In the case of a HTTP URL, queries typically translate into a -short transmission (the HTTP query) and a lengthy retrieval -process. Queries are not always short though, specially when -requesting forms (all the form data is attached within the -query), and also when requesting CGI programs. - - Regardless of query length, query sending is handled in -background. The thread that was initiated at TCP connecting time -has all the transmission framework already set up; at this point, -packet sending is just a matter of waiting for the -write signal (G_IO_OUT) to come and then sending the data. When -the socket gets ready for transmission, the data is sent using -IO_write. - - --------------- -Receiving data --------------- - - Although conceptually similar to sending queries, retrieving -data is very different as the data received can easily exceed the -size of the query by many orders of magnitude (for example when -downloading images or files). This is one of the main sources of -latency, the retrieval can take several seconds or even minutes -when downloading large files. - - The data retrieving process for a single file, that began by -setting up the expecting framework at TCP connecting time, simply -waits for the read signal (G_IO_IN). When it happens, the -low-level I/O engine gets called, the data is read into -pre-allocated buffers and the appropriate call-backs are -performed. Technically, whenever a G_IO_IN event is generated, -data is received from the socket file descriptor, by using the -IO_read function. This iterative process finishes upon EOF (or on -an error condition). - - ----------------------- -Closing the connection ----------------------- - - Closing a TCP connection requires four data segments, not an -impressive amount but twice the round trip time, which can be -substantial. When data retrieval finishes, socket closing is -triggered. There's nothing but a IO_close_fd call on the socket's -file descriptor. This process was originally designed to split -the four segment close into two partial closes, one when query -sending is done and the other when all data is in. This scheme is -not currently used because the write close also stops the reading -part. - - -The low-level I/O engine ------------------------- - - Dillo I/O is carried out in the background. This is achieved -by using low level file descriptors and signals. Anytime a file -descriptor shows activity, a signal is raised and the signal -handler takes care of the I/O. - - The low-level I/O engine ("I/O engine" from here on) was -designed as an internal abstraction layer for background file -descriptor activity. It is intended to be used by the cache -module only; higher level routines should ask the cache for its -URLs. Every operation that is meant to be carried out in -background should be handled by the I/O engine. In the case of -TCP sockets, they are created and submitted to the I/O engine for -any further processing. - - The submitting process (client) must fill a request structure -and let the I/O engine handle the file descriptor activity, until -it receives a call-back for finally processing the data. This is -better understood by examining the request structure: - - typedef struct { - gint Key; /* Primary Key (for klist) */ - gint Op; /* IORead | IOWrite | IOWrites */ - gint FD; /* Current File Descriptor */ - gint Flags; /* Flag array */ - glong Status; /* Number of bytes read, or -errno code */ - - void *Buf; /* Buffer place */ - size_t BufSize; /* Buffer length */ - void *BufStart; /* PRIVATE: only used inside IO.c! */ - - void *ExtData; /* External data reference (not used by IO.c) */ - void *Info; /* CCC Info structure for this IO */ - GIOChannel *GioCh; /* IO channel */ - guint watch_id; /* glib's event source id */ - } IOData_t; - - To request an I/O operation, this structure must be filled and -passed to the I/O engine. - - 'Op' and 'Buf' and 'BufSize' MUST be provided. - - 'ExtData' MAY be provided. - - 'Status', 'FD' and 'GioCh' are set by I/O engine internal -routines. - - When there is new data in the file descriptor, 'IO_callback' -gets called (by glib). Only after the I/O engine finishes -processing the data are the upper layers notified. - - -The I/O engine transfer buffer ------------------------------- - - The 'Buf' and 'BufSize' fields of the request structure -provide the transfer buffer for each operation. This buffer must -be set by the client (to increase performance by avoiding copying -data). - - On reads, the client specifies the amount and where to place -the retrieved data; on writes, it specifies the amount and source -of the data segment that is to be sent. Although this scheme -increases complexity, it has proven very fast and powerful. For -instance, when the size of a document is known in advance, a -buffer for all the data can be allocated at once, eliminating the -need for multiple memory reallocations. Even more, if the size is -known and the data transfer is taking the form of multiple small -chunks of data, the client only needs to update 'Buf' and -BufSize' to point to the next byte in its large preallocated -reception buffer (by adding the chunk size to 'Buf'). On the -other hand, if the size of the transfer isn't known in advance, -the reception buffer can remain untouched until the connection -closes, but the client must then accomplish the usual buffer -copying and reallocation. - - The I/O engine also lets the client specify a full length -transfer buffer when sending data. It doesn't matter (from the -client's point of view) if the data fits in a single packet or -not, it's the I/O engine's job to divide it into smaller chunks -if needed and to perform the operation accordingly. - - ------------------------------------------- -Handling multiple simultaneous connections ------------------------------------------- - - The previous sections describe the internal work for a single -connection, the I/O engine handles several of them in parallel. -This is the normal downloading behavior of a web page. Normally, -after retrieving the main document (HTML code), several -references to other files (typically images) and sometimes even -to other sites (mostly advertising today) are found inside the -page. In order to parse and complete the page rendering, those -other documents must be fetched and displayed, so it is not -uncommon to have multiple downloading connections (every one -requiring the whole fetching process) happening at the same time. - - Even though socket activity can reach a hectic pace, the -browser never blocks. Note also that the I/O engine is the one -that directs the execution flow of the program by triggering a -call-back chain whenever a file descriptor operation succeeds or -fails. - - A key point for this multiple call-back chained I/O engine is -that every single function in the chain must be guaranteed to -return quickly. Otherwise, the whole system blocks until it -returns. - - ------------ -Conclusions ------------ - - Dillo is currently in very stable beta state. It already shows -impressive performance, and its interactive ``feel'' is much -better than that of other web browsers. - - The modular structure of Dillo, and its reliance on GTK1 allow -it to be very small. Not every feature of HTML-4.01 has been -implemented yet, but no significant problems are foreseen in -doing this. - - The fact that Dillo's central I/O engine is written using -advanced features of POSIX and TCP/IP networking makes its -performance possible, but on the other hand this also means that -only a fraction of the interested hackers are able to work on it. - - A simple code base is critical when trying to attract hackers -to work on a project like this one. Using the GTK+ framework -helped both in creating the graphical user interface and in -handling the concurrency inside the browser. By having threads -communicate through pipes the need for explicit synchronization -is almost completely eliminated, and with it most of the -complexity of concurrent programming disappears. - - A clean, strictly applied layering approach based on clear -abstractions is vital in each programming project. A good, -supportive framework is of much help here. - - diff --git a/doc/Images.txt b/doc/Images.txt deleted file mode 100644 index 62082e48..00000000 --- a/doc/Images.txt +++ /dev/null @@ -1,129 +0,0 @@ - January 2009, --Jcid - -Update June 2015: See also doc/dw-images-and-backgrounds.doc, or -../html/dw-images-and-backgrounds.html (generated by doxygen). - - ------ - IMAGES - ------ - -* When a image tag is found within a HTML page, Html_tag_open_img -handles it by: - - - Parsing & getting attribute values. - - Creating a new image structure (DilloImage) and its - associated widget (DwImage). - i.e. If 'Image' is the var for the structure, then - 'Image->dw' is the widget. - - Requesting the image to be feeded by the cache. - - Sending some info to the browser interface. - -* The cache can either request the image data from the net, or -feed it directly from the dicache (decompressed image cache). - -* Both processes are somewhat different because the first one -requires to decode the image data into RGB format, and the second -one has the whole data already decoded. - -* Regardless of the RGB-data feeding method, the decoded data is -passed to the widget (DwImage) and drawn in a streamed way. - Note that INDEXED images are also decoded into RGB format. - - Html_tag_open_img // IMG element processing - Html_add_new_image // Read attributes, create image, add to HTML page - a_Image_new // Create a 'DilloImage' data structure, to coordinate - // decoded image-data transfer to an 'Imgbuf'. - Html_add_widget // Adds the dw::Image to the page - Html_load_image // Tells cache to retrieve image - - ---------------------- -Fetching from the net ---------------------- - -* a_Capi_open_url initiates the resource request, and when -finally the answer arrives, the HTTP header is examined for MIME -type and either the GIF or PNG or JPEG decoder is set to handle -the incoming data stream. - - Decoding functions: - a_Gif_callback, a_Jpeg_callback and a_Png_callback. - -* The decoding function calls the following dicache methods as -the data is processed (listed in order): - - a_Dicache_set_parms - a_Dicache_set_cmap (only for indexed-GIF images) - a_Dicache_write - a_Dicache_new_scan (MAY be called here or after set_cmap) - a_Dicache_close - - -* The dicache methods call the necessary functions to connect -with the widget code. This is done by calling image.c functions: - - a_Image_set_parms - a_Image_set_cmap - a_Image_write - a_Image_new_scan - a_Image_close - -* The functions in image.c make the required Dw calls. - - -------------------------- -Fetching from the dicache -------------------------- - -* a_Capi_open_url() tests the cache for the image, and the cache, -via a_Cache_open_url(), enqueues a client for it, without asking -the network for the data. When the client queue is processed (a -bit later), Dicache_image() is set as the callback. - -* When Dicache_image() is called, it sets the proper image data -decoder (RGB) and its data structure based on the entry's Type. -Then it substitutes itself with a_Dicache_callback() as the -handling function, and gets out of the way. - -* Thenceforth the rest of the functions calls is driven by -a_Dicache_callback(). - - ------------ -Misc. notes ------------ - -* Repeated images generate new cache clients, but they may share -the imgbuf. - Note: Currently there's no proper support for transparent -images (i.e. decode to RGBA), but most of the time they render -the background color OK. This is: when first loaded, repeated -images share a background color, but when cached they render -correctly ;-). There's no point in trying to fix this because the -correct solution is to decode to RGBA and let the toolkit (FLTK) -handle the transparency. - -* The first cache-client callback (Dicache_image()) is set when -the Content-type of the image is got. - -* Later on, when there's a shared imgbuf, the dicache's logic -avoids decoding it multiple times and reuses what's already done. - -* The dicache-entry and the Image structure hold bit arrays that -represent which rows have been decoded. - -* The image processing can be found in the following sources: - - - image.{cc,hh} - - dicache.[ch] - - gif.[ch], png.[ch], jpeg.[ch] - - dw/image.{cc,hh} - -* Bear in mind that there are four data structures for image -code: - - - DilloImage (image.hh) - - DICacheEntry (dicache.h) - - dw::Image (class Image in dw/image.hh) - - core::Imgbuf (imgbuf.hh) - diff --git a/doc/Makefile.am b/doc/Makefile.am index 71c7c32d..7b40d0a2 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -1,48 +1,10 @@ dist_doc_DATA = user_help.html man_MANS = dillo.1 EXTRA_DIST = \ - index.doc \ - lout.doc \ - dw-map.doc \ - dw-overview.doc \ - dw-usage.doc \ - dw-layout-views.doc \ - dw-layout-widgets.doc \ - dw-widget-sizes.doc \ - dw-changes.doc \ - dw-images-and-backgrounds.doc \ - dw-out-of-flow.doc \ - dw-out-of-flow-2.doc \ - fltk-problems.doc \ - rounding-errors.doc \ - uml-legend.doc \ - dw-line-breaking.doc \ - dw-example-screenshot.png \ - dw-viewport-without-scrollbar.png \ - dw-viewport-with-scrollbar.png \ - dw-size-of-widget.png \ - dw-style-box-model.png \ - dw-style-length-absolute.png \ - dw-style-length-percentage.png \ - dw-style-length-relative.png \ - dw-textblock-collapsing-spaces-1-1.png \ - dw-textblock-collapsing-spaces-1-2.png \ - dw-textblock-collapsing-spaces-2-1.png \ - dw-textblock-collapsing-spaces-2-2.png \ - dw-floats-01.png \ - not-so-simple-container.png \ - Cache.txt \ - Cookies.txt \ - Dillo.txt \ - Dw.txt \ - HtmlParser.txt \ - IO.txt \ - Images.txt \ - NC_design.txt \ - Dpid.txt \ - CCCwork.txt \ README \ - dillo.1.in + Cookies.txt \ + dillo.1.in \ + user_help.html dillo.1: $(srcdir)/dillo.1.in Makefile sed 's%/usr/local%${prefix}%g' < $(srcdir)/dillo.1.in > dillo.1 diff --git a/doc/NC_design.txt b/doc/NC_design.txt deleted file mode 100644 index 380787f6..00000000 --- a/doc/NC_design.txt +++ /dev/null @@ -1,127 +0,0 @@ - - _________________________________________________________________ - - Naming&Coding design - _________________________________________________________________ - - Dillo's code is divided into modules. For instance: bookmark, cache, - dicache, gif. - - Let's think of a module named "menu", then: - * Every internal routine of the module, should start with "Menu_" - prefix. - * "Menu_" prefixed functions are not meant to be called from outside - the module. - * If the function is to be exported to other modules (i.e. it will - be called from the outside), it should be wrapped with an "a_" - prefix. - - For instance: if the function name is "Menu_create", then it's an - internal function, but if we need to call it from the outside, then it - should be renamed to "a_Menu_create". - - Why the "a_" prefix? - Because of historical reasons. - And "a_Menu_create" reads better than "d_Menu_create" because the - first one suggests "a Menu create" function! - - Another way of understanding this is thinking of "a_" prefixed - functions as Dillo's internal library, and the rest ("Menu_" prefixed - in our example) as a private module-library. - - Indentation: - - Source code must be indented with 3 blank spaces, no Tabs. - Why? - Because different editors expand or treat tabs in several ways; 8 - spaces being the most common, but that makes code really wide and - we'll try to keep it within the 80 columns bounds (printer friendly). - - You can use: indent -kr -sc -i3 -bad -nbbo -nut -l79 myfile.c - - Function commenting: - - Every single function of the module should start with a short comment - that explains its purpose; three lines must be enough, but if you - think it requires more, enlarge it. - - /* - * Try finding the url in the cache. If it hits, send the contents - * to the caller. If it misses, set up a new connection. - */ - int a_Cache_open_url(const char *url, void *Data) - { - ... - ... - ... - } - - We also have the BUG:, TODO:, and WORKAROUND: tags. - Use them within source code comments to spot hidden issues. For - instance: - - /* BUG: this counter is not accurate */ - ++i; - - /* TODO: get color from the right place */ - a = color; - - /* WORKAROUND: the canonical way of doing it doesn't work yet. */ - ++a; ++a; ++a; - - Function length: - - Let's try to keep functions within the 45 lines boundary. This eases - code reading, following, understanding and maintenance. - - Functions with a single exit: - - It's much easier to follow and maintain functions with a single exit - point at the bottom (instead of multiple returns). The exception to - the rule are calls like dReturn_if_fail() at its head. - - dlib functions: - - * Dillo uses dlib extensively in its C sources. Before starting - to code something new, a good starting point is to check what - this library has to offer (check dlib/dlib.h). - * Memory management must be done using dNew, dNew0, dMalloc, dFree - and their relatives. - * For debugging purposes and error catching (not for normal flow): - dReturn_if_fail, dReturn_val_if_fail etc. are encouraged. - * The MSG macro is extensively used to output additional information - to the calling terminal. - - _________________________________________________________________ - - C++ - - Source code in C++ should follow the same rules with these exceptions: - - * Class method names are camel-cased and start with lowercase - e.g. appendInputMultipart - * Classes and types start uppercased - e.g. class DilloHtmlReceiver - * Class methods don't need to prefix its module name - e.g. links->get() - - We also try to keep the C++ relatively simple. Dillo does use - inheritance and templates, but that's about all. - - _________________________________________________________________ - - What do we get with this? - - * A clear module API for Dillo; every function prefixed "a_" is to - be used outside the module. - * A way to identify where the function came from (the - capitalized word is the module name). - * An inner ADT (Abstract data type) for the module that can be - isolated, tested and replaced independently. - * A two stage instance for bug-fixing. You can change the exported - function algorithms while someone else fixes the internal - module-ADT! - * A coding standard ;) - _________________________________________________________________ - - Naming&Coding design by Jorge Arellano Cid diff --git a/doc/README b/doc/README index 9736a32b..41c11d87 100644 --- a/doc/README +++ b/doc/README @@ -1,51 +1,5 @@ -README: Last update Jul 2009 +Last update: June 2015 -These documents cover dillo's internals. -For user help, see http://www.dillo.org/dillo3-help.html - --------------------------------------------------------------------------- - -These documents need a review. -*.txt were current with Dillo1, but many have since become more or - less out-of-date. -*.doc are doxygen source for the Dillo Widget (dw) component, and - were written for Dillo2. - -They will give you an overview of what's going on, but take them -with a pinch of salt. - - Of course I'd like to have *.txt as doxygen files too! -If somebody wants to make this conversion, please let me know -to assign higher priority to updating these docs. - --- -Jorge.- - - -------------------------------------------------------------------------- - FILE DESCRIPTION STATE - -------------------------------------------------------------------------- - NC_design.txt Naming&Coding design (Be sure to Current - read it before any other doc) - Dillo.txt General overview of the program Current - IO.txt Extensive introduction Current - Cache.txt Informative description Current - Images.txt Image handling and processing Current - HtmlParser.txt A versatile parser Current - Dw.txt The New Dillo Widget (Overview) Current - Imgbuf.txt Image buffers Pending - Selection.txt Selections, and link activation Current (?) - Cookies.txt Explains how to enable cookies Current - Dpid.txt Dillo plugin daemon Current - -------------------------------------------------------------------------- - - - * BTW, there's a small program (srch) within the src/ dir. It searches - tokens within the whole code (*.[ch]). It has proven very useful. - Ex: ./srch a_Image_write - ./srch todo: - - * Please submit your patches with 'hg diff'. - - - Happy coding! - --Jcid +This directory contains user documentation. Developer documentation is +only stored in the Hg repository at , in +the directory "devdoc", but not part of the tarball. diff --git a/doc/dw-changes.doc b/doc/dw-changes.doc deleted file mode 100644 index 7050df9a..00000000 --- a/doc/dw-changes.doc +++ /dev/null @@ -1,105 +0,0 @@ -/** \page dw-changes Changes to the GTK+-based Release Version - -

Changes in Dw

- -Related to the FLTK port, there have been many changes, this is a -(hopefully complete) list: - -
    -
  • Rendering abstraction, read \ref dw-overview and \ref dw-layout-views - for details. Some important changes: - -
      -
    • The underlying platform (e.g. the UI toolkit) is fully abstract, - there are several platform independent structures replacing - GTK+ structures, e.g. dw::core::Event. - -
    • The central class managing the widget tree is not anymore - GtkDwViewport, but dw::core::Layout. - -
    • Drawing is done via dw::core::View, a pointer is passed to - dw::core::Widget::draw. - -
    • The distinction between viewport coordinates and canvas - coordinates (formerly world coordinates) has been mostly - removed. (Only for views, it sometimes plays a role, see - \ref dw-layout-views). -
    - -
  • Cursors have been moved to dw::core::style, see - dw::core::style::Style::cursor. dw::core::Widget::setCursor is now - protected (and so only called by widget implementations). - -
  • World coordinates are now called canvas coordinates. - -
  • There is now a distinction between dw::core::style::StyleAttrs and - dw::core::style::Style. - -
  • There is no base class for container widgets anymore. The former - DwContainer::for_all has been removed, instead this functionality - is now done via iterators (dw::core::Widget::iterator, - dw::core::Iterator). - -
  • DwPage is now called dw::Textblock, and DwAlignedPage - dw::AlignedTextblock. - -
  • dw::Textblock, all sub classes of it, and dw::Table do not read - "limit_text_width" from the preferences, but get it as an argument. - (May change again.) - -
  • dw::Table has been rewritten. - -
  • Instead of border_spacing in the old DwStyle, there are two attributes, - dw::core::style::Style::hBorderSpacing and - dw::core::style::Style::vBorderSpacing, since CSS allowes to specify - two values. Without CSS, both attributes should have the same value. - -
  • Images are handled differently, see \ref dw-images-and-backgrounds. - -
  • Embedded UI widgets (formerly GtkWidget's) are handled differently, - see dw::core::ui. - -
  • DwButton has been removed, instead, embedded UI widgets are used. See - dw::core::ui and dw::core::ui::ComplexButtonResource. -
- -Dw is now written C++, the transition should be obvious. All "Dw" -prefixes have been removed, instead, namespaces are used now: - -
    -
  • dw::core contains the core, -
  • dw::core::style styles, -
  • dw::core::ui embedded UI resources, -
  • dw::fltk classes related to FLTK, and -
  • ::dw the widgets. -
- -

Documentation

- -The old documentation has been moved to: - - -
Old New -
Dw.txt - general part \ref dw-overview, \ref dw-usage, - \ref dw-layout-widgets, - \ref dw-widget-sizes -
remarks on specific widgets respective source files: dw::Bullet, - dw::core::ui::Embed -
DwImage.txt - signals dw::core::Layout::LinkReceiver -
rest dw::Image, - \ref dw-images-and-backgrounds -
Imgbuf.txt dw::core::Imgbuf, - \ref dw-images-and-backgrounds -
DwPage.txt dw::Textblock -
DwRender.txt \ref dw-overview, \ref dw-layout-views, - dw::core::ui -
DwStyle.txt dw::core::style -
DwTable.txt dw::Table -
DwWidget.txt dw::core::Widget, \ref dw-layout-widgets, - \ref dw-widget-sizes -
Selection.txt dw::core::SelectionState -
- -*/ diff --git a/doc/dw-example-screenshot.png b/doc/dw-example-screenshot.png deleted file mode 100644 index 94f272ab..00000000 Binary files a/doc/dw-example-screenshot.png and /dev/null differ diff --git a/doc/dw-floats-01.png b/doc/dw-floats-01.png deleted file mode 100644 index 116d36b3..00000000 Binary files a/doc/dw-floats-01.png and /dev/null differ diff --git a/doc/dw-grows.doc b/doc/dw-grows.doc deleted file mode 100644 index a0304ef9..00000000 --- a/doc/dw-grows.doc +++ /dev/null @@ -1,202 +0,0 @@ -/** \page dw-grows GROWS - Grand Redesign Of Widget Sizes - -This paper describes (will describe) some design changes to -calculating widget sizes. Goals are: - -- Simplification of widget size calculation by the parent widget; - dw::Textblock::calcWidgetSize, dw::OutOfFlowMgr::ensureFloatSize - etc. should become simpler or perhaps even obsolete. - -- Making the implementation of some features possible: - - - *max-width*, *max-height*, *min-width*, *min-height*; - - correct aspect ratio for images with only one percentage size defined; - - *display: inline-block*; - - <button>. - - -A short sketch -============== - -**dw::core::Widget::sizeRequest and dw::core::Widget::getExtremes will -return final results.** The caller does not have to correct the size, -e. g. when percentages are defined. As an example, -dw::Textblock::calcWidgetSize has already become much simpler. - -**A new hierarchy, *container*:** Aside from dw::core::Widget::parent -and dw::core::Widget::generator, there is a third hierarchy -dw::core::Widget::container, which is (unlike *generator*) always a -direct ancestor, and represents what in CSS is called *containing -block*. Containers are important to define the "context size", which -is (not solely) used for percentage sizes. - -(There is another "containing block", dw::Textblock::containingBlock; -these may be consolidated some day.) - -**The process of size calculation is split between the widget itself -and its container:** - -- The container provides some abstract methods: - dw::core::Widget::getAvailWidthOfChild, - dw::core::Widget::getAvailHeightOfChild, - dw::core::Widget::correctRequisitionOfChild, and - dw::core::Widget::correctExtremesOfChild, which can be used in the - actual implementation of dw::core::Widget::sizeRequestImpl; - different containers with different ways how to arrange their - children will implement these methods in a different way. (Simple - example: the *available width* for children within a textblock is - the *available width* for the textblock itself, minus - margin/border/padding; on the other hand, it is completely different - for children of tables, for which a complex column width calculation - is used.) - -- The actual size calculation is, however, controlled by the widget - itself, which only *uses* these methods above. - -
- Update: This is not fully correct; the parents are also involved - for calculating available widths and heights, at least when CSS 'width' - and 'height' are not set.
- -**Size hints are removed.** Instead, the container methods in the -previous paragraph are used. Changes of container sizes (especially -viewport the size) are handled in a different way. - -**Extremes are extended by intrinsic values.** In some cases (see -dw::Table::forceCalcCellSizes, case *minWidth* > *totalWidth*, for an -example) it is useful to know about minimal and maximal width of a -widget independent of CSS attributes. For this, dw::core::Extremes is -extended by: - -- dw::core::Extremes::minWidthIntrinsic and -- dw::core::Extremes::maxWidthIntrinsic. - -The rules for the calculation: - -1. If a widget has no children, it calculates *minWidthIntrinsic* and - *maxWidthIntrinsic* as those values not affected by CSS hints. - (dw::core::Widget::correctExtremes will not change these values.) -2. A widget must calculate *minWidthIntrinsic* and *maxWidthIntrinsic* - from *minWidthIntrinsic* and *maxWidthIntrinsic* of its children, - and *minWidth* and *maxWidth* from *minWidth* and *maxWidth* of its - children. -3. At the end, *minWidth* and *maxWidth* of a widget are corrected by - CSS attributes. (dw::core::Widget::correctExtremes will do this.) - -
- Notice: Currently, dw::core::Widget::getExtremesImpl must - set all four members in dw::core::Extremes; this may change.
- -Another **extension of extremes: *adjustmentWidth*.** This is used as -minimum for the width, when "adjust_min_width" (or, -"adjust_table_min_width", respectively) is set. - -The rules for the calculation: - -1. If a widget has no children, it can choose a suitable value, - typically based on dw::core::Extremes::minWidth and - dw::core::Extremes::minWidthIntrinsic. -2. A widget must calculate *adjustmentWidth* from *adjustmentWidth* of - its children. - -*Note:* An implementation of dw::core::Widget::getExtremesImpl may set -this value *after* calling dw::core::Widget::correctExtremesOfChild, -so that it cannot be used for the correction of extremes. In this case -*useAdjustmentWidth = false* should be passed to -dw::core::Widget::correctExtremesOfChild. On the other hand, if known -before, *useAdjustmentWidth* should be set to *true*. - -Rules for *new* methods related to resizing -=========================================== - -- Of course, *sizeRequestImpl* may (should) call *correctRequisition*, - and *getExtremesImpl* may (should) call *correctExtremes*. - -- *sizeRequestImpl* (and *correctRequisition*) is allowed to call - *getAvailWidth* and *getAvailHeight* with *forceValue* set, but - *getExtremesImpl* (and *correctExtremes*) is allowed to call these - only with *forceValue* unset. - -- For this reason, *sizeRequestImpl* is indeed allowed to call - *getExtremes* (dw::Table does so), but the opposite - (*getExtremesImpl* calling *sizeRequest*) is not allowed - anymore. (Before GROWS, the standard implementation - dw::core::Widget::getExtremesImpl did so.) - -- Finally, *getAvailWidth* and *getAvailHeight* may call - *getExtremes*, if and only if *forceValue* is set. - -Here is a diagram showing all permitted dependencies: - -\dot -digraph G { - node [shape=record, fontname=Helvetica, fontsize=10, color="#c0c0c0"]; - edge [arrowhead="open", arrowtail="none", color="#404040"]; - - "sizeRequest[Impl]" -> "getExtremes[Impl]"; - "sizeRequest[Impl]" -> correctRequisition; - "getExtremes[Impl]" -> correctExtremes; - "sizeRequest[Impl]" -> "getAvail[Width|Height] (true)"; - "getExtremes[Impl]" -> "getAvail[Width|Height] (false)"; - correctRequisition -> "getAvail[Width|Height] (true)"; - correctExtremes -> "getAvail[Width|Height] (false)"; - "getAvail[Width|Height] (true)" -> "getExtremes[Impl]"; -} -\enddot - -Open issues -=========== - -**Do CSS size dimensions override intrinsic sizes in all cases?** If a -textblock needs at least, say, 100 pixels width so that the text can -be read, but has a specification "width: 50px", should half of the -text be invisible? Or should the width be corrected again to 100 -pixels? - -Currently, in the CSS size specification is honoured in all cases, -with one exception: see dw::Textblock::sizeRequestImpl and see -dw::Textblock::getExtremesImpl (the time when -dw::core::Widget::correctRequisition and -dw::core::Widget::correctExtremes, respectively, is called). - -*Not* honouring the CSS size specification in all cases could improve -readability in some cases, so this could depend on a user preference. - -**Update:** There is now a dillorc option adjust_min_width, -which is implemented for widths, but not heights (since it is based on -width extremes, but there are currently no height extremes). - -Another problem is that in most cases, there is no clippping, so that -contents may exceed the allocation of the widget, but redrawing is not -necessarily triggered. - -**Percentage values for margins and paddings, as well as negative -margins** are interesting applications, but have not been considered -yet. For negative margins, a new attribute -dw::core::Widget::extraSpace could solve the problem of widgets -sticking out of the allocation of parent. - -**Clarify percentage heights.** Search in widget.cc, and compare -section 10.5 ('height') of the CSS 2.1 specification to section 10.2 -('width'). - -**Fast queue resize does not work fully.** Example: run -*test/dw-simple-container-test* (dw_simple_container_test.cc), resize -(best maximize) the window and follow (e. g. by using RTFL) what -happens in consequence of dw::core::Layout::viewportSizeChanged. The -dw::SimpleContainer in the middle is not affected, so only the two -dw::Textblock's (at the top and at the bottom) call queueResize with -*fast = true*, and so get *NEEDS_RESIZE* set; but since it is not set -for the dw::SimpleContainer, *sizeRequest* is never called for the -bottom dw::Textblock. - -There does not seem to be a real case for this problem in dillo, since -all widgets which may contain other widgets (except -dw::SimpleContainer, which is not used outside tests) use the -available width and height (dw::core::Widget::usesAvailWidth and -dw::core::Widget::usesAvailHeight), and so are always affected by -viewport size changes. - -*/ diff --git a/doc/dw-images-and-backgrounds.doc b/doc/dw-images-and-backgrounds.doc deleted file mode 100644 index 8f07766a..00000000 --- a/doc/dw-images-and-backgrounds.doc +++ /dev/null @@ -1,235 +0,0 @@ -/** \page dw-images-and-backgrounds Images and Backgrounds in Dw - -Image Buffers -============= - -Representation of the image data is done by dw::core::Imgbuf, see -there for details. Drawing is done by dw::core::View -(dw::core::View::drawImage). - -Since dw::core::Imgbuf provides memory management based on reference -counting, there may be an 1-to-n relation from image renderers (image -widgets or backgrounds, see below) and dw::core::Imgbuf. Since -dw::core::Imgbuf does not know about renderers, but just provides -rendering functionality, the caller must (typically after calling -dw::core::Imgbuf::copyRow) notify all renderers connected to the -buffer. - - -Image Renderer -============== - -Generally, there are no restrictions on how to manage -dw::core::Imgbuf; but to handle image data from web resources, the -interface dw::core::ImgRenderer should be implemented. It is again -wrapped by DilloImage (to make access from the C part possible, since -dw::core::ImgRenderer is written in C++), which is referenced by -DilloWeb. There are two positions where retrieving image data is -initiated: - -- Html_load_image: for embedded images (implemented by dw::Image, - which implements dw::core::ImgRenderer); -- StyleEngine::apply (search for "case - CSS_PROPERTY_BACKGROUND_IMAGE"): for backgrond images; there are - some implementations of dw::core::ImgRenderer within the context of - dw::core::style::StyleImage. - -Both are described in detail below. Notice that the code is quite -similar; only the implementation of dw::core::ImgRenderer differs. - -At this time, dw::core::ImgRenderer has got two methods (see more -documentation there): - -- dw::core::ImgRenderer::setBuffer, -- dw::core::ImgRenderer::drawRow, -- dw::core::ImgRenderer::finish, and -- dw::core::ImgRenderer::fatal. - - -Images -====== - -This is the simplest renderer, displaying an image. For each row to be -drawn, - -- first dw::core::Imgbuf::copyRow, and then -- for each dw::Image, dw::Image::drawRow must be called, with the same - argument (no scaling is necessary). - -dw::Image automatically scales the dw::core::Imgbuf, the root buffer -should be passed to dw::Image::setBuffer. - -\see dw::Image for more details. - - -Background Images -================= - -Since background images are style resources, they are associated with -dw::core::style::Style, as dw::core::style::StyleImage, which is -handled in a similar way (reference counting etc.) as -dw::core::style::Color and dw::core::style::Font, although it is -concrete and not platform-dependant. - -The actual renderer (passed to Web) is an instance of -dw::core::ImgRendererDist (distributes all calls to a set of other -instances of dw::core::ImgRenderer), which contains two kinds of -renderers: - -- one instance of dw::core::style::StyleImage::StyleImgRenderer, which - does everything needed for dw::core::style::StyleImage, and -- other renderers, used externally (widgets etc.), which are added by - dw::core::style::StyleImage::putExternalImgRenderer (and removed by - dw::core::style::StyleImage::removeExternalImgRenderer). - -This diagram gives an comprehensive overview: - -\dot -digraph G { - node [shape=record, fontname=Helvetica, fontsize=10]; - edge [arrowhead="open", dir="both", arrowtail="none", - labelfontname=Helvetica, labelfontsize=10, color="#404040", - labelfontcolor="#000080"]; - fontname=Helvetica; fontsize=10; - - subgraph cluster_dw_style { - style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; - - Style [URL="\ref dw::core::style::Style"]; - StyleImage [URL="\ref dw::core::style::StyleImage"]; - Imgbuf [URL="\ref dw::core::Imgbuf", color="#a0a0a0"]; - StyleImgRenderer - [URL="\ref dw::core::style::StyleImage::StyleImgRenderer"]; - ImgRenderer [URL="\ref dw::core::ImgRenderer", color="#ff8080"]; - ImgRendererDist [URL="\ref dw::core::ImgRendererDist"]; - ExternalImgRenderer - [URL="\ref dw::core::style::StyleImage::ExternalImgRenderer", - color="#a0a0a0"]; - ExternalWidgetImgRenderer - [URL="\ref dw::core::style::StyleImage::ExternalWidgetImgRenderer", - color="#a0a0a0"]; - } - - subgraph cluster_dw_layout { - style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; - - Layout [URL="\ref dw::core::Layout"]; - LayoutImgRenderer [URL="\ref dw::core::Layout::LayoutImgRenderer"]; - } - - subgraph cluster_dw_widget { - style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; - - Widget [URL="\ref dw::core::Widget", color="#a0a0a0"]; - WidgetImgRenderer [URL="\ref dw::core::Widget::WidgetImgRenderer"]; - } - - subgraph cluster_dw_textblock { - style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; - - Textblock [URL="\ref dw::Textblock"]; - Word [URL="\ref dw::Textblock::Word"]; - WordImgRenderer [URL="\ref dw::Textblock::WordImgRenderer"]; - SpaceImgRenderer [URL="\ref dw::Textblock::SpaceImgRenderer"]; - } - - Style -> StyleImage [headlabel="*", taillabel="1"]; - StyleImage -> Imgbuf [headlabel="*", taillabel="1"]; - StyleImage -> StyleImgRenderer [headlabel="1", taillabel="1"]; - StyleImage -> ImgRendererDist [headlabel="1", taillabel="1"]; - ImgRendererDist -> StyleImgRenderer [headlabel="1", taillabel="1"]; - ImgRendererDist -> ImgRenderer [headlabel="1", taillabel="*"]; - - ImgRenderer -> ImgRendererDist [arrowhead="none", arrowtail="empty", - dir="both", style="dashed"]; - ImgRenderer -> StyleImgRenderer [arrowhead="none", arrowtail="empty", - dir="both", style="dashed"]; - ImgRenderer -> ExternalImgRenderer [arrowhead="none", arrowtail="empty", - dir="both", style="dashed"]; - ExternalImgRenderer -> ExternalWidgetImgRenderer [arrowhead="none", - arrowtail="empty", dir="both", style="dashed"]; - - Layout -> LayoutImgRenderer [headlabel="1", taillabel="0..1"]; - ExternalImgRenderer -> LayoutImgRenderer [arrowhead="none", - arrowtail="empty", dir="both", style="dashed"]; - - Widget -> WidgetImgRenderer [headlabel="1", taillabel="0..1"]; - ExternalWidgetImgRenderer -> WidgetImgRenderer [arrowhead="none", - arrowtail="empty", dir="both", style="dashed"]; - - Textblock -> Word [headlabel="1", taillabel="*"]; - Word -> WordImgRenderer [headlabel="1", taillabel="0..1"]; - Word -> SpaceImgRenderer [headlabel="1", taillabel="0..1"]; - ExternalWidgetImgRenderer -> WordImgRenderer [arrowhead="none", - arrowtail="empty", dir="both", style="dashed"]; - WordImgRenderer -> SpaceImgRenderer [arrowhead="none", arrowtail="empty", - dir="both", style="dashed"]; -} -\enddot - -
[\ref uml-legend "legend"]
- - -Memory management ------------------ - -dw::core::style::StyleImage extends lout::signal::ObservedObject, so -that deleting this instance can be connected to code dealing with -cache clients etc. See StyleImageDeletionReceiver and how it is -attached in StyleEngine::apply ("case CSS_PROPERTY_BACKGROUND_IMAGE"). - - -Bugs and Things Needing Improvement -=================================== - -(Mostly related to image backgrounds, when not otherwise mentioned.) - -High Priority -------------- - -**Configurability, security/privacy aspects, etc.,** which are -currently available for image widgets, should be adopted. Perhaps some -more configuration options specially for background images. - - -Medium Priority ---------------- - -**Background-attachment** is not yet implemented, and will be postponed. - -**Calls to dw::core::ImgRenderer::fatal** are incomplete. As an -example, it is not called, when connecting to a server fails. (And so, -as far as I see, no cache client is started.) - - -Low Priority ------------- - -**Alpha support:** (not related to image backgrounds) currently alpha -support (and also colormap management) is done in dicache, while -dw::Image is only created with type RGB. This leads to several problems: - -- One dicache entry (representing an image related to the same URL), - which has only one background color, may refer to different images - with different background colors. -- The dicache only handles background colors, not background images. - -The solution is basicly simple: keep alpha support out of dicache; -instead implement RGBA in dw::Image. As it seems, the main problem is -alpha support in FLTK/X11. - - -Solved (Must Be Documented) ---------------------------- - -*Drawing background images row by row may become slow. As an -alternative, dw::core::ImgRenderer::finish could be used. However, -drawing row by row could become an option.* There is now -dw::core::style::drawBackgroundLineByLine, which can be changed in the -code, and is set to *false*. The old code still exists, so changing -this to *true* activates again drawing line by line. - -(For image widgets, this could also become an option: in contexts, -when image data is retrieved in a very fast way.) - -*/ diff --git a/doc/dw-layout-views.doc b/doc/dw-layout-views.doc deleted file mode 100644 index d1118489..00000000 --- a/doc/dw-layout-views.doc +++ /dev/null @@ -1,256 +0,0 @@ -/** \page dw-layout-views Layout and Views - -Rendering of Dw is done in a way resembling the model-view pattern, at -least formally. Actually, the counterpart of the model, the layout -(dw::core::Layout), does a bit more than a typical model, namely the -layouting (delegated to the widget tree, see \ref dw-layout-widgets), -and the view does a bit less than a typical view, i.e. only the actual -drawing. - -Additionally, there is a structure representing common properties of -the platform. A platform is typically related to the underlying UI -toolkit, but other uses may be thought of. - -This design helps to archieve two important goals: - -
    -
  • Abstraction of the actual drawing, by different implementations - of dw::core::View. - -
  • It makes portability simple. -
- - -

Viewports

- -Although the design implies that the usage of viewports should be -fully transparent to the layout module, this cannot be fully achieved, -for the following reasons: - -
    -
  • Some features, which are used on the level of dw::core::Widget, - e.g. anchors, refer to scrolling positions. - -
  • Size hints (see \ref dw-layout-widgets) depend on the viewport - sizes, e.g. when the user changes the window size, and so also - the size of a viewport, the text within should be rewrapped. -
- -Therefore, dw::core::Layout keeps track of the viewport size, the -viewport position, and even the thickness of the scrollbars, they are -relevant, see below for more details. -If a viewport is not used, however, the size is not defined. - -Whether a given dw::core::View implementation is a viewport or not, is -defined by the return value of dw::core::View::usesViewport. If this -method returns false, the following methods need not to be implemented -at all: - -
    -
  • dw::core::View::getHScrollbarThickness, -
  • dw::core::View::getVScrollbarThickness, -
  • dw::core::View::scrollTo, and -
  • dw::core::View::setViewportSize. -
- -

Scrolling Positions

- -The scrolling position is the canvas position at the upper left corner -of the viewport. Views using viewports must - -
    -
  1. change this value on request (dw::core::View::scrollTo), and -
  2. tell other changes to the layout, e.g. caused by user events - (dw::core::Layout::scrollPosChanged). -
- -Applications of scrolling positions (anchors, test search etc.) are -handled by the layout, in a way fully transparent to the view. - -

Scrollbars

- -A feature of the viewport size model are scrollbars. There may be a -vertical scrollbar and a horizontal scrollbar, displaying the -relationship between canvas and viewport height or width, -respectively. If they are not needed, they are hidden, to save screen -space. - -Since scrollbars decrease the usable space of a view, dw::core::Layout -must know how much space they take. The view returns, via -dw::core::View::getHScrollbarThickness and -dw::core::View::getVScrollbarThickness, how thick they will be, when -visible. - -Viewport sizes, which denote the size of the viewport widgets, include -scrollbar thicknesses. When referring to the viewport \em excluding -the scrollbars space, we will call it "usable viewport size", this is -the area, which is used to display the canvas. - -

Drawing

- -A view must implement several drawing methods, which work on the whole -canvas. If it is necessary to convert them (e.g. into -dw::fltk::FltkViewport), this is done in a way fully transparent to -dw::core::Widget and dw::core::Layout, instead, this is done by the -view implementation. - -There exist following situations: - -
    -
  • A view gets an expose event: It will delegate this to the - layout (dw::core::Layout::draw), which will then pass it to the - widgets (dw::core::Widget::draw), with the view as a parameter. - Eventually, the widgets will call drawing methods of the view. - -
  • A widget requests a redraw: In this case, the widget will - delegate this to the layout (dw::core::Layout::queueDraw), which - delegates it to the view (dw::core::View::queueDraw). - Typically, the view will queue these requests for efficiency. - -
  • A widget requests a resize: This case is described below, in short, - dw::core::View::queueDrawTotal is called for the view. -
- -If the draw method of a widget is implemented in a way that it may -draw outside of the widget's allocation, it should draw into a -clipping view. A clipping view is a view related to the actual -view, which guarantees that the parts drawn outside are discarded. At -the end, the clipping view is merged into the actual view. Sample -code: - -\code -void Foo::draw (dw::core::View *view, dw::core::Rectangle *area) -{ - // 1. Create a clipping view. - dw::core::View clipView = - view->getClippingView (allocation.x, allocation.y, - allocation.width, getHeight ()); - - // 2. Draw into clip_view - clipView->doSomeDrawing (...); - - // 3. Draw the children, they receive the clipping view as argument. - dw::core::Rectangle *childArea - for () { - if (child->intersects (area, &childArea)) - child->draw (clipView, childArea); - } - - // 4. Merge - view->mergeClippingView (clipView); -} -\endcode - -A drawing process is always embedded into calls of -dw::core::View::startDrawing and dw::core::View::finishDrawing. An -implementation of this may e.g. use backing pixmaps, to prevent -flickering. - - -

Sizes

- -In the simplest case, the view does not have any influence on -the canvas size, so it is told about changes of the -canvas size by a call to dw::core::View::setCanvasSize. This happens -in the following situations: - -
    -
  • dw::core::Layout::addWidget, -
  • dw::core::Layout::removeWidget (called by dw::core::Widget::~Widget), - and -
  • dw::core::Layout::queueResize (called by - dw::core::Widget::queueResize, when a widget itself requests a size - change). -
- -

Viewports

- -There are two cases where the viewport size changes: - -
    -
  • As an reaction on a user event, e.g. when the user changes the - window size. In this case, the view delegates this - change to the layout, by calling - dw::core::Layout::viewportSizeChanged. - -
  • The viewport size may also depend on the visibility of UI - widgets, which depend on the world size, e.g scrollbars, - generally called "viewport markers". This is described in a separate - section. -
- -After the creation of the layout, the viewport size is undefined. When -a view is attached to a layout, and this view can already specify -its viewport size, it may call -dw::core::Layout::viewportSizeChanged within the implementation of -dw::core::Layout::setLayout. If not, it may do this as soon as the -viewport size is known. - -Generally, the scrollbars have to be considered. If e.g. an HTML page -is rather small, it looks like this: - -\image html dw-viewport-without-scrollbar.png - -If some more data is retrieved, so that the height exceeds the -viewport size, the text has to be rewrapped, since the available width -gets smaller, due to the vertical scrollbar: - -\image html dw-viewport-with-scrollbar.png - -Notice the different line breaks. - -This means circular dependencies between these different sizes: - -
    -
  1. Whether the scrollbars are visible or not, determines the - usable space of the viewport. - -
  2. From the usable space of the viewport, the size hints for the - toplevel are calculated. - -
  3. The size hints for the toplevel widgets may have an effect on its - size, which is actually the canvas size. - -
  4. The canvas size determines the visibility of the scrollbarss. -
- -To make an implementation simpler, we simplify the model: - -
    -
  1. For the calls to dw::core::Widget::setAscent and - dw::core::Widget::setDescent, we will always exclude the - horizontal scrollbar thickness (i.e. assume the horizontal - scrollbar is used, although the visibility is determined correctly). - -
  2. For the calls to dw::core::Widget::setWidth, we will calculate - the usable viewport width, but with the general assumption, that - the widget generally gets higher. -
- -This results in the following rules: - -
    -
  1. Send always (when it changes) dw::core::Layout::viewportHeight - minus the maximal value of dw::core::View::getHScrollbarThickness as - argument to dw::core::Widget::setAscent, and 0 as argument to - dw::core::Widget::setDescent. - -
  2. There is a flag, dw::core::Layout::canvasHeightGreater, which is set - to false in the following cases: - -
      -
    • dw::core::Layout::addWidget, -
    • dw::core::Layout::removeWidget (called by dw::core::Widget::~Widget), - and -
    • dw::core::Layout::viewportSizeChanged. -
    - - Whenever the canvas size is calculated (dw::core::Layout::resizeIdle), - and dw::core::Layout::canvasHeightGreater is false, a test is made, - whether the widget has in the meantime grown that high, that the second - argument should be set to true (i.e. the vertical scrollbar gets visible). - As soon as and dw::core::Layout::canvasHeightGreater is true, no such test - is done anymore. -
- -*/ diff --git a/doc/dw-layout-widgets.doc b/doc/dw-layout-widgets.doc deleted file mode 100644 index e0215562..00000000 --- a/doc/dw-layout-widgets.doc +++ /dev/null @@ -1,267 +0,0 @@ -/** \page dw-layout-widgets Layout and Widgets - -Both, the layouting and the drawing is delegated to a tree of -widgets. A widget represents a given part of the document, e.g. a text -block, a table, or an image. Widgets may be nested, so layouting and -drawing may be delegated by one widget to its child widgets. - -Where to define the borders of a widget, whether to combine different -widgets to one, or to split one widget into multiple ones, should be -considered based on different concerns: - -
    -
  • First, there are some restrictions of Dw: - -
      -
    • The allocation (this is the space a widget allocates at - a time) of a dillo widget is always rectangular, and -
    • the allocation of a child widget must be a within the allocation - of the parent widget. -
    - -
  • Since some widgets are already rather complex, an important goal - is to keep the implementation of the widget simple. - -
  • Furthermore, the granularity should not be too fine, because of the - overhead each single widget adds. -
- -For CSS, there will be a document tree on top of Dw, this will be -flexible enough when mapping the document structure on the widget -structure, so you should not have the document structure in mind. - -

Sizes

- -\ref dw-widget-sizes - - -

Styles

- -Each widget is assigned a style, see dw::core::style for more -informations. - - -

Iterators

- -Widgets must implement dw::core::Widget::iterator. There are several -common iterators: - -
    -
  • dw::core::EmptyIterator, and -
  • dw::core::TextIterator. -
- -Both hide the constructor, use the \em create method. - -These simple iterators only iterate through one widget, it does not -have to iterate recursively through child widgets. Instead, the type -dw::core::Content::WIDGET is returned, and the next call of -dw::core::Iterator::next will return the piece of contents \em after -(not within) this child widget. - -This makes implementation much simpler, for recursive iteration, there -is dw::core::DeepIterator. - - -

Anchors and Scrolling

- -\todo This section is not implemented yet, after the implementation, - the documentation should be reviewed. - -Here is a description, what is to be done for a widget -implementation. How to jump to anchors, set scrolling positions -etc. is described in \ref dw-usage. - -

Anchors

- -Anchors are position markers, which are identified by a name, which is -unique in the widget tree. The widget must care about anchors in three -different situations: - -
    -
  1. Adding an anchor is inititiated by a specific widget method, e.g. - dw::Textblock::addAnchor. Here, dw::core::Widget::addAnchor must be - called, - -
  2. Whenever the position of an anchor is changed, - dw::core::Widget::changeAnchor is called (typically, this is done - in the implementation of dw::core::Widget::sizeAllocateImpl). - -
  3. When a widget is destroyed, the anchor must be removed, by calling - dw::core::Widget::removeAnchor. -
- -All these methods are delegated to dw::core::Layout, which manages -anchors centrally. If the anchor in question has been set to jump to, -the viewport position is automatically adjusted, see \ref -dw-usage. - - -

Drawing

- -In two cases, a widget has to be drawn: - -
    -
  1. as a reaction on an expose event, -
  2. if the widget content has changed and it needs to be redrawn. -
- -In both cases, drawing is done by the implementation of -dw::core::Widget::draw, which draws into the view. - - -Each view provides some primitive methods for drawing, most should be -obvious. Note that the views do not know anything about dillo -widgets, and so coordinates have to be passed as canvas coordinates. - -A widget may only draw in its own allocation. If this cannot be -achieved, a clipping view can be used, this is described in -\ref dw-layout-views. Generally, drawing should then look like: - -\code -void Foo::draw (dw::core::View *view, dw::core::Rectangle *area) -{ - // 1. Create a clipping view. - dw::core::View clipView = - view->getClippingView (allocation.x, allocation.y, - allocation.width, getHeight ()); - - // 2. Draw into clip_view - clipView->doSomeDrawing (...); - - // 3. Draw the children, they receive the clipping view as argument. - dw::core::Rectangle *childArea - for () { - if (child->intersects (area, &childArea)) - child->draw (clipView, childArea); - } - - // 4. Merge - view->mergeClippingView (clipView); -} -\endcode - -Clipping views are expensive, so they should be avoided when possible. - -The second argument to dw::core::Widget::draw is the region, which has -to be drawn. This may (but needs not) be used for optimization. - -If a widget contains child widgets, it must explicitly draw these -children (see also code example above). For this, there is the useful -method dw::core::Widget::intersects, which returns, which area of the -child must be drawn. - -

Explicit Redrawing

- -If a widget changes its contents, so that it must be redrawn, it must -call dw::core::Widget::queueDrawArea or -dw::core::Widget::queueDraw. The first variant expects a region within -the widget, the second will cause the whole widget to be redrawn. This -will cause an asynchronous call of dw::core::Widget::draw. - -If only the size changes, a call to dw::core::Widget::queueResize is -sufficient, this will also queue a complete redraw (see \ref -dw-widget-sizes.) - - -

Mouse Events

- -A widget may process mouse events. The view (\ref dw-layout-views) -passes mouse events to the layout, which then passes them to the -widgets. There are two kinds of mouse events: - -
    -
  • events returning bool, and -
  • events returning nothing (void). -
- -The first group consists of: - -
    -
  • dw::core::Widget::buttonPressImpl, -
  • dw::core::Widget::buttonReleaseImpl, and -
  • dw::core::Widget::motionNotifyImpl. -
- -For these events, a widget returns a boolean value, which denotes, -whether the widget has processed this event (true) or not (false). In -the latter case, the event is delegated according to the following -rules: - -
    -
  1. First, this event is passed to the bottom-most widget, in which - allocation the mouse position is in. -
  2. If the widget does not process this event (returning false), it is - passed to the parent, and so on. -
  3. The final result (whether \em any widget has processed this event) is - returned to the view. -
- -The view may return this to the UI toolkit, which then interprets this -in a similar way (whether the viewport, a UI widget, has processed -this event). - -These events return nothing: - -
    -
  • dw::core::Widget::enterNotifyImpl and -
  • dw::core::Widget::leaveNotifyImpl. -
- -since they are bound to a widget. - -When processing mouse events, the layout always deals with two -widgets: the widget, the mouse pointer was in, when the previous mouse -event was processed, (below called the "old widget") and the widget, -in which the mouse pointer is now ("new widget"). - -The following paths are calculated: - -
    -
  1. the path from the old widget to the nearest common ancestor of the old - and the new widget, and -
  2. the path from this ancestor to the new widget. -
- -For the widgets along these paths, dw::core::Widget::enterNotifyImpl -and dw::core::Widget::leaveNotifyImpl are called. - -

Signals

- -If a caller outside of the widget is interested in these events, he -can connect a dw::core::Layout::LinkReceiver. For those events with a -boolean return value, the results of the signal emission is regarded, -i.e. the delegation of an event to the parent of the widget can be -stopped by a signal receiver returning true, even if the widget method -returns false. - -First, the widget method is called, then (in any case) the signal is -emitted. - -

Selection

- -If your widget has selectable contents, it should delegate the events -to dw::core::SelectionState (dw::core::Layout::selectionState). - - -

Miscellaneous

- -

Cursors

- -Each widget has a cursor, which is set by -dw::core::Widget::setCursor. If a cursor is assigned to a part of a -widget, this widget must process mouse events, and call -dw::core::Widget::setCursor explicitly. - -(This will change, cursors should become part of -dw::core::style::Style.) - -

Background

- -Backgrounds are part of styles -(dw::core::style::Style::backgroundColor). If a widget assigns -background colors to parts of a widget (as dw::Table does for rows), -it must call dw::core::Widget::setBgColor for the children inside this -part. - -*/ diff --git a/doc/dw-line-breaking.doc b/doc/dw-line-breaking.doc deleted file mode 100644 index 14ab97c4..00000000 --- a/doc/dw-line-breaking.doc +++ /dev/null @@ -1,470 +0,0 @@ -/** \page dw-line-breaking Changes in Line-Breaking and Hyphenation - -
Info: -Should be incorporated into dw::Textblock.
- -Introduction -============ - -For the implementation of hyphenation in dillo, not only a -hyphenation algorithm was implemented, but also, the line breaking was -changed to a simple optimization per line. Aside from the improvement -by this change per se, an important aspect is the introduction of -"penalties". Before this change, dillo put all words into a line which -fitted into it; now, a "badness" is calculated for a possible -breakpoint, and the best breakpoint, i. e. the breakpoint with the -smallest value for "badness", is chosen. This can be simply refined -to define "good" and "bad" breakpoints by assigning a "penalty"; the -best breakpoint is then the one with the smallest value of "badness + -penalty". Details can be found below. - -Example: Normal spaces have a penalty of 0, while hyphenation points -get a penalty of, say, 1, since hyphenation is generally considered as -a bit "ugly" and should rather be avoided. Consider a situation where -the word "dillo" could be hyphenated, with the following badnesses: - -- before "dillo": 0.6; -- between "dil-" and "lo": 0.2; -- after "dillo": 0.5. - -Since the penalty is added, the last value is the best one, so "dillo" -is put at the end of the line, without hyphenation. - -Under other circumstances (e. g. narrower lines), the values -might be different: - -- before "dillo": infinite; -- between "dil-" and "lo": 0.3; -- after "dillo": 1.5. - -In this case, even the addition of the penalty makes hyphenation the -best choice. - - -Literature -========== - -Breaking Paragraphs Into Lines ------------------------------- - -Although dillo does not (yet?) implement the algorithm TEX -uses for line breaking, this document shares much of the notation used -by the article *Breaking Paragraphs Into Lines* by Donald E. Knuth and -Michael F. Plass; originally published in: Software -- Practice and -Experience **11** (1981), 1119-1184; reprinted in: *Digital -Typography* by Donalt E. Knuth, CSLI Publications 1999. Anyway an -interesting reading. - -Hyphenation ------------ - -Dillo uses the algorithm by Frank Liang, which is described in his -doctoral dissertation found at http://www.tug.org/docs/liang/. There -is also a description in chapter H ("Hyphenation") of *The -TEXbook* by Donald E. Knuth, Addison-Wesley 1984. - -Pattern files can be found at -http://www.ctan.org/tex-archive/language/hyphenation. - - -Overview of Changes -=================== - -Starting with this change, dw/textblock.cc has been split up; anything -related to line breaking has been moved into -dw/textblock_linebreaking.cc. This will also be done for other aspects -like floats. (Better, however, would be a clean logical split.) - -An important change relates to the way that lines are added: before, -dillo would add a line as soon as a new word for this line was -added. Now, a line is added not before the *last* word of this line is -known. This has two important implications: - -- Some values in dw::Textblock::Line, which represented values - accumulated within the line, could be removed, since now, these - values can be calculated simply in a loop. -- On the other hand, this means that some words may not belong to any - line. For this reason, in some cases (e. g. in - dw::Textblock::sizeRequestImpl) dw::Textblock::showMissingLines is - called, which creates temporary lines, which must, under other - circumstances, be removed again by - dw::Textblock::removeTemporaryLines, since they have been created - based on limited information, and so possibly in a wrong way. (See - below for details.) - -When a word can be hyphenated, an instance of dw::Textblock::Word is -used for each part. Notice that soft hyphens are evaluated -immediately, but automatic hyphenation is done in a lazy way (details -below), so the number of instances may change. There are some new -attributes: only when dw::Textblock::Word::canBeHyphenated is set to -*true*, automatic hyphenation is allowed; it is set to false when soft -hyphens are used for a word, and (of course) by the automatic -hyphenation itself. Furthermore, dw::Textblock::Word::hyphenWidth -(more details in the comment there) has to be included when -calculating line widths. - -Some values should be configurable: dw::Textblock::HYPHEN_BREAK, the -penalty for hyphens. Also dw::Textblock::Word::stretchability, -dw::Textblock::Word::shrinkability, which are both set in -dw::Textblock::addSpace. - - -Criteria for Line-Breaking -========================== - -Before these changes to line breaking, a word (represented by -dw::Textblock::Word) had the following attributes related to -line-breaking: - -- the width of the word itself, represented by - dw::Textblock::Word::size; -- the width of the space following the word, represented by - dw::Textblock::Word::origSpace. - -In a more mathematical notation, the \f$i\f$th word has a width -\f$w_i\f$ and a space \f$s_i\f$. - -A break was possible, when there was a space between the two words, -and the first possible break was chosen. - -With hyphenation, the criteria are refined. Hyphenation should only be -used when otherwise line breaking results in very large spaces. We -define: - -- the badness \f$\beta\f$ of a line, which is greater the more the - spaces between the words differ from the ideal space; -- a penalty \f$p\f$ for any possible break point. - -The goal is to find those break points, where \f$\beta + p\f$ is -minimal. - -Examples for the penalty \f$p\f$: - -- 0 for normal line breaks (between words); -- \f$\infty\f$ to prevent a line break at all costs; -- \f$-\infty\f$ to force a line -- a positive, but finite, value for hyphenation points. - -So we need the following values: - -- \f$w_i\f$ (the width of the word \f$i\f$ itself); -- \f$s_i\f$ (the width of the space following the word \f$i\f$); -- the stretchability \f$y_i\f$, a value denoting how much the space - after word\f$i\f$ can be stretched (typically \f${1\over 2} s_i\f$ - for justified text; otherwise 0, since the spaces are not - stretched); -- the shrinkability \f$y_i\f$, a value denoting how much the space - after word\f$i\f$ can be shrunken (typically \f${1\over 3} s_i\f$ - for justified text; otherwise 0, since the spaces are not shrinked); -- the penalty \f$p_i\f$, if the line is broken after word \f$i\f$; -- a width \f$h_i\f$, which is added, when the line is broken after - word \f$i\f$. - -\f$h_i\f$ is the width of the hyphen, if the word \f$i\f$ is a part of -the hyphenated word (except the last part); otherwise 0. - -Let \f$l\f$ be the (ideal) width (length) of the line, which is -e. at the top given by the browser window width. Furthermore, all words -from \f$a\f$ to \f$b\f$ are added to the line. \f$a\f$ is fixed: we do -not modify the previous lines anymore; but our task is to find a -suitable \f$b\f$. - -We define: - -\f[W_a^b = \sum_{i=a}^{b} w_i + \sum_{i=a}^{b-1} s_i + h_b\f] - -\f[Y_a^b = {Y_0}_a^b + \sum_{i=a}^{b-1} y_i\f] - -\f[Z_a^b = {Z_0}_a^b + \sum_{i=a}^{b-1} z_i\f] - - -\f$W_a^b\f$ is the total width, \f$Y_a^b\f$ the total stretchability, -and \f$Z_a^b\f$ the total shrinkability. \f${Y_0}_a^b\f$ and -\f${Z_0}_a^b\f$ are the stretchability and shrinkability defined per -line, and applied at the borders; they are 0 for justified text, but -\f${Y_0}_a^b\f$ has a positive value otherwise, see below for details. - -Furthermore the *adjustment ratio* \f$r_a^b\f$: - -- in the ideal case that \f$W_a^b = l\f$: \f$r_a^b = 0\f$; -- if \f$W_a^b < l\f$: \f$r_a^b = (l - W_a^b) / Y_a^b\f$ - (\f$r_a^b < 0\f$ in this case); -- if \f$W_a^b > l\f$: \f$r_a^b = (l - W_a^b) / Z_a^b\f$ - (\f$r_a^b < 0\f$ in this case). - -The badness \f$\beta_a^b\f$ is defined as follows: - -- if \f$r_a^b\f$ is undefined or \f$r_a^b < -1\f$: \f$\beta_a^b = \infty\f$; -- otherwise: \f$\beta_a^b = |r_a^b|^3\f$ - -The goal is to find the value of \f$b\f$ where \f$\beta_a^b + p_b\f$ -is minimal. (\f$a\f$ is given, since we do not modify the previous -lines.) - -After a couple of words, it is not predictable whether this minimum -has already been reached. There are two cases where this is possible -for a given \f$b'\f$: - -- \f$\beta_{b'}^a = \infty\f$ (line gets too tight): - \f$a \le b < b'\f$, the minimum has to be searched between these two - values; -- \f$p_{b'} = -\infty\f$ (forced line break): - \f$a \le b \le b'\f$ (there may be another minimum of - \f$\beta_a^b\f$ before; note the \f$\le\f$ instead of \f$<\f$). - -This leads to a problem that the last words of a text block are not -displayed this way, since they do not fulfill these rules for being -added to a line. For this reason, there are "temporary" lines already -described above. - -(Note that the actual calculation differs from this description, since -integer arithmetic is used for performance, which make the actual -code more complicated. See dw::Textblock::BadnessAndPenalty for -details.) - -Ragged Borders --------------- - -For other than justified text (left-, right-aligned and centered), the -spaces between the words are not shrinked or stretched (so \f$y_i\f$ -and \f$z_i\f$ are 0), but additional space is added to the left or -right border or to both. For this reason, an additional stretchability -\f${Y_0}_a^b\f$ is added (see definition above). Since this space at -the border is 0 in an ideal case (\f$W_a^b = l\f$), it cannot be -shrunken, so \f${Z_0}_a^b\f$ is 0. - -This is not equivalent to the calculation of the total stretchability -as done for justified text, since in this case, the stretchability -depends on the number of words: consider the typical case that all -spaces and stretchabilities are equal (\f$y_a = y_{a + 1} = \ldots = -y_b\f$). With \f$n\f$ words, the total strechability would be \f$n -\cdot y_a\f$, so increase with an increasing number of words -(\f$y_a\f$ is constant). This is correct for justified text, but for -other alignments, where only one space (or two, for centered text) is -changed, this would mean that a line with many narrow words is more -stretchable than a line with few wide words. - -It is obvious that left-aligned text can be handled in the same way as -right-aligned text. [... Centered text? ...] - -The default value for the stretchability is the line height without -the space between the lines (more precisely: the maximum of all word -heights). The exact value not so important when comparing different -possible values for the badness \f$\beta_a^b\f$, when \f${Y_0}_a^b\f$ -is nearly constant for different \f$b\f$ (which is the case for the -actual value), but it is important for the comparison with penalties, -which are constant. To be considered is also that for non-justified -text, hyphenation is differently (less) desirable; this effect can be -achieved by enlarging the stretchability, which will lead to a smaller -badness, and so make hyphenation less likely. The user can configure -the stretchability by changing the preference value -*stretchability_factor* (default: 1.0). - -(Comparison to TEX: Knuth and Plass describe a method for -ragged borders, which is effectively the same as described here (Knuth -1999, pp. 93--94). The value for the stretchability of the line -is slightly less, 1 em (ibid., see also p. 72 for the -definition of the units). However, this article suggests a value for -the hyphenation penalty, which is ten times larger than the value for -justified text; this would suggest a larger value for -*stretchability_factor*.) - - -Hyphens -======= - -Words (instances of dw::Textblock::Word), which are actually part of a -hyphenated word, are always drawn as a whole, not seperately. This -way, the underlying platform is able to apply kerning, ligatures, etc. - -Calculating the width of such words causes some problems, since it is -not required that the width of text "AB" is identical to the width of -"A" plus the width of "B", just for the reasons mentioned above. It -gets even a bit more complicated, since it is required that a word -part (instance of dw::Textblock::Word) has always the same length, -independent of whether hyphenation is applied or not. Furthermore, the -hyphen length is fixed for a word; for practical reasons, it is always -the width of a hyphen, in the given font. - -For calculating the widths, consider a word of four syllables: -A-B-C-D. There are 3 hyphenation points, and so 23 = 8 -possible ways of hyphenation: ABCD, ABC-D, AB-CD, AB-C-D, A-BCD, -A-BC-D, A-B-CD, A-B-C-D. (Some of them, like the last one, are only -probable for very narrow lines.) - -Let w(A), w(B), w(C), w(D) be the word widths (part of -dw::Textblock::Word::size), which have to be calculated, and l be a -shorthand for dw::core::Platform::textWidth. Without considering -this problem, the calculation would be simple: w(A) = l(A) -etc. However, it gets a bit more complicated. Since all -non-hyphenations are drawn as a whole, the following conditions can be -concluded: - -- from drawing "ABCD" (not hyphenated at all): w(A) + w(B) + w(C) + - w(D) = l(ABCD); -- from drawing "BCD", when hyphenated as "A-BCD" ("A-" is not - considered here): w(B) + w(C) + w(D) = l(BCD); -- likewise, from drawing "CD" (cases "AB-CD" and "A-B-CD"): w(C) + - w(D) = l(CD); -- finally, for the cases "ABC-D", "AB-C-D", "A-BC-D", and "A-B-C-D": - w(D) = l(D). - -So, the calculation is simple: - -- w(D) = l(D) -- w(C) = l(CD) - w(D) -- w(B) = l(BCD) - (w(C) + w(D)) -- w(A) = l(ABCD) - (w(B) + w(C) + w(D)) - -For calculation the hyphen widths, the exact conditions would be -over-determined, even when the possibility for individual hyphen -widths (instead of simply the text width of a hyphen character) would -be used. However, a simple approach of fixed hyphen widths will have -near-perfect results, so this is kept simple. - - -Automatic Hyphenation -===================== - -When soft hyphens are used, words are immediately divided into -different parts, and so different instances of -dw::Textblock::Word. Automatic hyphenation (using Liang's algorithm) -is, however, not applied always, but only when possibly needed, after -calculating a line without hyphenation: - -- When the line is tight, the last word of the line is hyphenated; - possibly this will result in a line with less parts of this word, - and so a less tight line. -- When the line is loose, and there is another word (for the next - line) available, this word is hyphenated; possibly, some parts of - this word are taken into this line, making it less loose. - -After this, the line is re-calculated. - -A problem arrises when the textblock is rewrapped, e. g. when the -user changes the window width. In this case, some new instances of -dw::Textblock::Word must be inserted into the word list, -dw::Textblock::words. This word list is implemented as an array, which -is dynamically increased; a simple approach would involve moving all -of the n elements after position i, so -n - i steps are necessary. This would not be a -problem, since O(n) steps are necessary; however, this will be -necessary again for the next hyphenated word (at the end of a -following line), and so on, so that -(n - i1) + -(n - i2) + ..., with -i1 < i2 < ..., -which results in O(n2) steps. For this reason, the word -list is managed by the class lout::misc::NotSoSimpleVector, which uses -a trick (a second array) to deal with exactly this problem. See there -for more details. - - -Tests -===== - -There are test HTML files in the test directory. Also, there is -a program testing automatic hyphenation, test/liang, which can -be easily extended. - - -Bugs and Things Needing Improvement -=================================== - -High Priority -------------- - -None. - -Medium Priority ---------------- - -None. - -Low Priority ------------- - -**Mark the end of a paragraph:** Should dw::core::Content::BREAK still -be used? Currently, this is redundant to -dw::Textblock::BadnessAndPenalty. - -Solved (Must Be Documented) ---------------------------- - -These have been solved recently and should be documented above. - -*Bugs in hyphenation:* There seem to be problems when breaking words -containing hyphens already. Example: "Abtei-Stadt", which is divided -into "Abtei-" and "Stadt", resulting possibly in -"Abtei--[new line]Stadt". See also below under -"Medium Priority", on how to deal with hyphens and dashes. - -**Solution:** See next. - -*Break hyphens and dashes:* The following rules seem to be relevant: - -- In English, an em-dash is used with no spaces around. Breaking - before and after the dash should be possible, perhaps with a - penalty > 0. (In German, an en-dash (Halbgeviert) with spaces around - is used instead.) -- After a hyphen, which is part of a compound word, a break should be - possible. As described above ("Abtei-Stadt"), this collides with - hyphenation. - -Where to implement? In the same dynamic, lazy way like hyphenation? As -part of hyphenation? - -Notice that Liang's algorithm may behave different regarding hyphens: -"Abtei-Stadt" is (using the patterns from CTAN) divided into "Abtei-" -and "Stadt", but "Nordrhein-Westfalen" is divided into "Nord", -"rhein-West", "fa", "len": the part containing the hyphen -("rhein-West") is untouched. (Sorry for the German words; if you have -got English examples, send them me.) - -**Solution for both:** This has been implemented in -dw::Textblock::addText, in a similar way to soft hyphens. Liang's -algorithm now only operates on the parts: "Abtei" and "Stadt"; -"Nordrhein" and "Westfalen". - -*Hyphens in adjacent lines:* It should be simple to assign a larger -penalty for hyphens, when the line before is already hyphenated. This -way, hyphens in adjacent lines are penalized further. - -**Solved:** There are always two penalties. Must be documented in -detail. - -*Incorrect calculation of extremes:* The minimal width of a text block -(as part of the width extremes, which are mainly used for tables) is -defined by everything between two possible breaks. A possible break -may also be a hyphenation point; however, hyphenation points are -calculated in a lazy way, when the lines are broken, and not when -extremes are calculated. So, it is a matter of chance whether the -calculation of the minimal width will take the two parts "dil-" and -"lo" into account (when "dillo" has already been hyphenated), or only -one part, "dillo" (when "dillo" has not yet been hyphenated), -resulting possibly in a different value for the minimal width. - -Possible strategies to deal with this problem: - -- Ignore. The implications should be minimal. -- Any solution will make it neccessary to hyphenate at least some - words when calculating extremes. Since the minimal widths of all - words are used to calculate the minimal width of the text block, the - simplest approach will hyphenate all words. This would, of course, - eliminate the performance gains of the current lazy approach. -- The latter approach could be optimized in some ways. Examples: (i) - If a word is already narrower than the current accumulated value for - the minimal width, it makes no sense to hyphenate it. (ii) In other - cases, heuristics may be used to estimate the number of syllables, - the width of the widest of them etc. - -**Solved:** Hyphenated parts of a word are not considered anymore for -width extremes, but only whole words. This is also one reason for the -introduction of the paragraphs list. - -**Also:** - -- Configuration of penalties. - -*/ diff --git a/doc/dw-map.doc b/doc/dw-map.doc deleted file mode 100644 index aebeb7da..00000000 --- a/doc/dw-map.doc +++ /dev/null @@ -1,59 +0,0 @@ -/** \page dw-map Dillo Widget Documentation Map - -This maps includes special documentations as well as longer comments -in the sources. Arrows denote references between the documents. - -\dot -digraph G { - rankdir=LR; - node [shape=record, fontname=Helvetica, fontsize=8]; - fontname=Helvetica; fontsize=8; - - dw_overview [label="Dillo Widget Overview", URL="\ref dw-overview"]; - dw_usage [label="Dillo Widget Usage", URL="\ref dw-usage"]; - dw_layout_views [label="Layout and Views", URL="\ref dw-layout-views"]; - dw_layout_widgets [label="Layout and Widgets", - URL="\ref dw-layout-widgets"]; - dw_widget_sizes [label="Sizes of Dillo Widgets", - URL="\ref dw-widget-sizes"]; - dw_changes [label="Changes to the GTK+-based Release Version", - URL="\ref dw-changes"]; - dw_images_and_backgrounds [label="Images and Backgrounds in Dw", - URL="\ref dw-images-and-backgrounds"]; - dw_Image [label="dw::Image", URL="\ref dw::Image"]; - dw_core_Imgbuf [label="dw::core::Imgbuf", URL="\ref dw::core::Imgbuf"]; - dw_core_SelectionState [label="dw::core::SelectionState", - URL="\ref dw::core::SelectionState"]; - dw_core_style [label="dw::core::style", URL="\ref dw::core::style"]; - dw_Table [label="dw::Table", URL="\ref dw::Table"]; - dw_Textblock [label="dw::Textblock", URL="\ref dw::Textblock"]; - dw_core_ui [label="dw::core::ui", URL="\ref dw::core::ui"]; - - dw_overview -> dw_changes; - dw_overview -> dw_usage; - dw_overview -> dw_core_style; - dw_overview -> dw_core_ui; - dw_overview -> dw_images_and_backgrounds; - dw_overview -> dw_layout_widgets; - dw_overview -> dw_widget_sizes; - dw_overview -> dw_layout_views; - - dw_usage -> dw_Table; - dw_usage -> dw_Textblock; - dw_usage -> dw_core_style; - dw_usage -> dw_core_ui; - dw_usage -> dw_images_and_backgrounds; - - dw_layout_widgets -> dw_widget_sizes; - dw_layout_widgets -> dw_core_SelectionState; - - dw_widget_sizes -> dw_Table; - dw_widget_sizes -> dw_Textblock; - - dw_images_and_backgrounds -> dw_core_Imgbuf; - dw_images_and_backgrounds -> dw_Image; - - dw_core_style -> dw_Textblock; -} -\enddot -*/ \ No newline at end of file diff --git a/doc/dw-out-of-flow-2.doc b/doc/dw-out-of-flow-2.doc deleted file mode 100644 index d9d70565..00000000 --- a/doc/dw-out-of-flow-2.doc +++ /dev/null @@ -1,69 +0,0 @@ -/** \page dw-out-of-flow-2 Handling Elements Out Of Flow (notes 2) - -This has to be integrated into \ref dw-out-of-flow. - -Constructing a page with floats -------------------------------- -When a page is constructed (dw::Textblock::addWord), the *generating* -block tells the positions of floats (or, generally, widgets out of -flow) via dw::OutOfFlowMgr::tellPosition. This method considers -already collisions with other floats (only previous floats; floats -following this float are not considered); after the call, -dw::OutOfFlowMgr::getBorder will return correct values. - -dw::OutOfFlowMgr::tellPosition also checks for overlaps of this float -with other textblocks, except this textblock (the *generator*, which -is just constructed, so nothing has to be done). The fact that the -position of the float is the top, and so the float has only an -allocation below this position, leads to the effect that only the -textblocks following the generator are affected. (**Check:** Can the -search be limited here?) When a page is constructed, no textblocks -should be following the generating block, so no textblocks are -affected. - -**Todo:** Clarify details of line breaking (\ref dw-line-breaking). - -Float changes its size ----------------------- -The float itself will call queueResize, which will result in a call of -markSizeChange for the *containing* block, which will then call -dw::OutOfFlowMgr::markSizeChange. Here, the vloat is only *marked* as -dirty; the size will be calculated later (in -dw::OutOfFlowMgr::ensureFloatSize). - -This will trigger the resize idle function, so sizeRequest and -sizeAllocate for all floats and textblocks. In this run, -dw::OutOfFlowMgr::hasRelationChanged will return *true*, and so result -in a call of dw::Textblock::borderChanged, and trigger a second run of -the resize idle function, dealing correctly with the new size. - -(This case is handles in a not perfectly optimal way, since two runs -of the resize idle function are neccessary; but size changes of floats -is not a very common case. - -When a page is constructed (see above), a changing size of a float -currently constructed typically only affects the most bottom -textblock; the other textblocks are not covered by this float.) - -**Error:** In this case, new collisions are not yet considered. - - -Changing the width of the page ------------------------------- - -When the page width is changed, this will result in a reconstruction -of the page; see *Constructing a page with floats*. Anyway, checking -for overlaps will play a more important role. This is handled in an -optimal way by dw::OutOfFlowMgr::hasRelationChanged. - -**Check:** Are "cascades" avoided, like this: - -1. All textblocks are constructed. A float in textblock 1 overlaps - with textblock 2, so dw::Textblock::borderChanged is called for - textblock 2. -2. In another resize idle run, textblock 2 is constructed again. A - float in textblock 2 overlaps with textblock 3, so that - dw::Textblock::borderChanged is called for textblock 3. -3. Etc. - -*/ \ No newline at end of file diff --git a/doc/dw-out-of-flow-floats.doc b/doc/dw-out-of-flow-floats.doc deleted file mode 100644 index 53c6b220..00000000 --- a/doc/dw-out-of-flow-floats.doc +++ /dev/null @@ -1,121 +0,0 @@ -/** \page dw-out-of-flow-floats Handling Elements Out Of Flow: Floats - -(Note: Bases on work at , I plan -to split the documentation on elements out of flow into different -parts: general part, floats, positioned elements. In this document, -informations about floats are collected.) - - -GB lists and CB lists -===================== - -Floats generated by a block which is not yet allocated are initially -put into a list related to the *generator*: - -- dw::OutOfFlowMgr::TBInfo::leftFloatsGB or -- dw::OutOfFlowMgr::TBInfo::rightFloatsGB. - -These lists are also called GB lists. - -Floats of allocated generators are put into lists related to the -*container* (called CB lists): - -- dw::OutOfFlowMgr::leftFloatsCB or -- dw::OutOfFlowMgr::rightFloatsCB. - -As soon as the container is allocated, all floats are moved from the -GB lists to the CB lists (dw::OutOfFlowMgr::sizeAllocateStart → -dw::OutOfFlowMgr::moveFromGBToCB). - -Here, it is important to preserve the *generation order* (for reasons, -see below: *Sorting floats*), i. e. the order in which floats have -been added (dw::OutOfFlowMgr::addWidgetOOF). This may become a bit -more complicated in a case like this: - - - - - -
-
float 1
-
-
float 2
-
-
float 3
-
- - -The floats are generated in this order: - -- \#fl-1 (generated by \#bl-1), -- \#fl-2 (generated by \#bl-2), -- \#fl-3 (generated by \#bl-1). - -Since the floats must be moved into the CB list in this order, it -becomes clear that the floats from one GB list cannot be moved at -once. For this reason, each float is assigned a "mark", which is -different from the last one as soon as the generator is *before* the -generator of the float added before. In the example above, there are -three generators: body, \#bl-1, and \#bl-2 (in this order), and floats -are assigned these marks: - -- \#fl-1: 0, -- \#fl-2: also 0, -- \#fl-3 is assigned 1, since its generator (\#bl-1) lies before the - last generator (\#bl-2). - -dw::OutOfFlowMgr::moveFromGBToCB will then iterate over all marks, so -that the generation order is preserved. - - -Sorting floats -============== - -Floats are sorted, to make binary search possible, in these lists: - -- for each generator: dw::OutOfFlowMgr::TBInfo::leftFloatsGB and - dw::OutOfFlowMgr::TBInfo::rightFloatsGB; -- for the container: dw::OutOfFlowMgr::leftFloatsCB and - dw::OutOfFlowMgr::rightFloatsCB. - -The other two lists, dw::OutOfFlowMgr::leftFloatsAll and -dw::OutOfFlowMgr::rightFloatsAll are not sorted at all. - -New floats are always added to the end of either list; this order is -called *generation order*. See also above: *GB lists and CB lists*. - -On the other hand, there are different sorting criteria, implemented -by different comparators, so that different kinds of keys may be used -for searching. These sorting criteria are equivalent to the generation -order. - -dw::OutOfFlowMgr::Float::CompareSideSpanningIndex compares -*sideSpanningIndex* (used to compare floats to those on the respective -other side); if you look at the definition -(dw::OutOfFlowMgr::addWidgetOOF) it becomes clear that this order is -equivalent to the generation order. - -dw::OutOfFlowMgr::Float::CompareGBAndExtIndex compares *externalIndex* -for floats with same generators, otherwise: (i) if one generator (T1) -is a direct anchestor of the other generator (T2), the child of T1, -which is an anchestor of, or identical to, T2 is compared to the float -generated by T1, using *externalIndex*, as in this example: - - T1 -+-> child --> ... -> T2 -> Float - `-> Float - -Otherwise, the two blocks are compared, according to their position in -dw::OutOfFlowMgr::tbInfos: - - common anchestor -+-> ... --> T1 -> Float - `-> ... --> T2 -> Float - -This is equivalent to the generation order, as long it is ensured that -*externalIndex* reflects the generation order within a generating -block, for both floats and child blocks. - -dw::OutOfFlowMgr::Float::ComparePosition ... - -*/ diff --git a/doc/dw-out-of-flow.doc b/doc/dw-out-of-flow.doc deleted file mode 100644 index ea4a52bc..00000000 --- a/doc/dw-out-of-flow.doc +++ /dev/null @@ -1,214 +0,0 @@ -/** \page dw-out-of-flow Handling Elements Out Of Flow - - -
Info: -Should be incorporated into dw::Textblock.
- -Introduction -============ - -This texts deals with both floats and absolute positions, which have -in common that there is a distinction between generating block and -containing block (we are here using the same notation as in the -CSS 2 specification). Consider this snippet (regarding floats): - - -
    -
  • Some text.
  • -
  • -
    Some longer text, so - that the effect described in this passage can be - demonstrated. -
    - Some more and longer text.
  • -
  • Final text. Plus some more to demonstrate how text flows - around the float on the right side.
  • -
- -which may be rendered like this - -\image html dw-floats-01.png - -The float (the DIV section, yellow in the image) is defined -("generated") within the list item (blue), so, in CSS 2 terms, the -list item is the generating block of the float. However, as the image -shows, the float is not contained by the list item, but another block, -several levels above (not shown here). In terms of ::dw, this means -that the dw::Textblock representing the float cannot be a child of the -dw::Textblock representing the generating block, the list item, since -the allocation of a child widget must be within the allocation of the -parent widget. Instead, to each dw::Textblock, another dw::Textblock -is assigned as the containing box. - -(Notice also that other text blocks must regard floats to calculate -their borders, and so their size. In this example, the following list -item (green) must consider the position of the float. This is -discussed in detail in the next section.) - -Both in this text and the code, generating and containing block are -abbreviated with **GB** and **CB**, respectively. - - -Implementation overview -======================= - -Widget level ------------- -The terms *generating block* and *containing block* have been raised -to a higher level, the one of dw::core::Widget, and are here called -*generating widget* and *containing widget*. To represent the -distinction, the type of dw::core::Content has been split into three -parts: - -- If a widget is out of flow, the generating widget keeps a reference - with the type dw::core::Content::WIDGET_OOF_REF, while the - containing block refers to it as dw::core::Content::WIDGET_OOF_CONT. -- For widgets within flow, dw::core::Content::WIDGET_IN_FLOW is used. - -Notice that in the first case, there are two pieces of content -referring to the same widget. - -An application of this distinction is iterators. [TODO: more. And -still missing: DeepIterator may need the generating parent widget in -some cases.] - - -Textblock level ---------------- -Both dw::Textblock::notifySetAsTopLevel and -dw::Textblock::notifySetParent set the member -dw::Textblock::containingBlock appropriately, (according to rules -which should be defined in this document). - -Handling widgets out of flow is partly the task of the new class -dw::OutOfFlowMgr, which is stored by dw::Textblock::outOfFlowMgr, but -only for containing blocks. Generating blocks should refer to -*containingBlock->outOfFlowMgr*. (Perhaps dw::OutOfFlowMgr may become -independent of dw::Textblock.) - -dw::Textblock::addWidget is extended, so that floats and absolutely -positioned elements can be added. Notice that not *this* widget, but -the containing block becomes the parent of the newly added child, if -it is out of flow. dw::Textblock::addWidget decides this by calling -dw::OutOfFlowMgr::isOutOfFlow. (See new content types above.) - -dw::core::Widget::parentRef has become a new representation. Before, -it represented the line numer. Now (least signifant bit left): - - +---+ - - - +---+---+- - - - - -+---+---+---+---+ - | line number | 0 | - +---+ - - - +---+---+- - - - - -+---+---+---+---+ - - +---+ - - - +---+---+- - - - - -+---+---+---+---+ - | left float index | 0 | 0 | 1 | - +---+ - - - +---+---+- - - - - -+---+---+---+---+ - - +---+ - - - +---+---+- - - - - -+---+---+---+---+ - | right float index | 1 | 0 | 1 | - +---+ - - - +---+---+- - - - - -+---+---+---+---+ - - +---+ - - - +---+---+- - - - - -+---+---+---+---+ - | absolutely positioned index | 1 | 1 | - +---+ - - - +---+---+- - - - - -+---+---+---+---+ - -Details are hidden by static inline methods of dw::OutOfFlowMgr. - - -The sizeRequest/sizeAllocate problem -======================================== - -*See also:* \ref dw-widget-sizes, especially the section *Rules for -Methods Related to Resizing*. - -The size/position model of ::dw consists mainly of the following two -steps: - -1. First, the size of the toplevel widget is calculated. Size - calculation typically depends on the sizes of the widgets, which - are calculated recursively, but not more. -2. After this, the toplevel widget is allocated at position (0, 0), - with the previosly calculated size. Each widget must allocate its - children; here, the condition for the toplevel widget (allocated - size equals requested size) is not necessary; instead, each widget - may be allocated at every size. - -Especially for floats, this model becomes a bit difficult, for reasons -described below. For the solutions, much is centralized at the level -of the containing block, which delegates most to an instance of -dw::OutOfFlowMgr (details below). - -**The size of a widget depends on the size not only of the children.** -In the example above, the last list item (green, following the -generating list item) must know the size of the the float (which is -not a child or, generally, descendant) to determine the borders, which -is done in dw::Textblock::sizeRequestImpl. - -For this, the size model has been extended (see \ref dw-widget-sizes, -section *Rules for Methods Related to Resizing*): *sizeRequest* can be -called within *sizeRequestImpl* for other widgets that children (with -some caution). Namely, dw::Textblock::sizeRequestImpl calls -dw::core::Widget::sizeRequest for the float, via -dw::OutOfFlowMgr::getBorder and dw::OutOfFlowMgr::ensureFloatSize. - -**The size of a widget depends on the allocation of another widget.** -In the example above, both list items (blue and green) must know the -position of the float widget, within dw::Textblock::sizeRequestImpl, -to calculate the borders. The position, however, is stored in the -allocation, which is typically calculated later. - -Here, two cases must be distinguished. The position of a float is -always **relative to its generating block**, so for calculating the -borders for the generating block, the allocation needs not to be -know. For other textblocks, it needs to be known, so the calculation -of the borders will ignore floats generated by other textblocks, until -all widgets are allocated. The latter will call (when neccessary) -dw::core::Widget::queueResize, so that all border calculations are -repeated. See below (*hasRelationChanged*) for details. - -Generally, this pattern (distinguishing between GB and CB) can be -found everywhere in dw::OutOfFlowMgr. - -For details see: - -- dw::OutOfFlowMgr::getLeftBorder, dw::OutOfFlowMgr::getRightBorder, - dw::OutOfFlowMgr::getBorder (called by the first two), and - especially, dw::OutOfFlowMgr::getFloatsListForTextblock (called by - the latter), where these three cases are distinguished; -- dw::OutOfFlowMgr::sizeAllocateStart, - dw::OutOfFlowMgr::sizeAllocateEnd which are called by the containing - block. - -(This could be solved in a more simple, elegant way, when -*sizeRequest* would depend on the position. This is, however, only a -vague idea, perhaps not even feasible, and for which there are no -concrete plans, certainly not in \ref dw-grows.) - - -Implementation details -====================== - -- CB and GB lists (general pattern) (see previous section) -- binary search; different search criteria, how they accord -- lastLeftTBIndex, lastRightTBIndex etc. -- limitiation of search; extIndex etc. - - -How *hasRelationChanged* works -============================== - -... - - -Integration of line breaking and floats -======================================= - -(Positioning of floats, loop, recent works.) - - -Absolute and fixed positiones -============================= - -See . - -*/ \ No newline at end of file diff --git a/doc/dw-overview.doc b/doc/dw-overview.doc deleted file mode 100644 index 0c4ffb53..00000000 --- a/doc/dw-overview.doc +++ /dev/null @@ -1,158 +0,0 @@ -/** \page dw-overview Dillo Widget Overview - -Note: If you are already familiar with the Gtk+-based version of Dw, -read \ref dw-changes. - - -The module Dw (Dillo Widget) is responsible for the low-level rendering of -all resources, e.g. images, plain text, and HTML pages (this is the -most complex type). Dw is \em not responsible for parsing HTML, or -decoding image data. Furthermore, the document tree, which is planned -for CSS, is neither a part of Dw, instead, it is a new module on top -of Dw. - -The rendering, as done by Dw, is split into two phases: - -
    -
  • the \em layouting, this means calculating the exact positions of - words, lines, etc. (in pixel position), and -
  • the \em drawing, i.e. making the result of the layouting visible - on the screen. -
- -The result of the layouting allocates an area, which is called -\em canvas. - -

Structure

- -The whole Dw module can be split into the following parts: - -\dot -digraph G { - node [shape=record, fontname=Helvetica, fontsize=10]; - edge [arrowhead="open", fontname=Helvetica, fontsize=10, - labelfontname=Helvetica, labelfontsize=10, - color="#404040", labelfontcolor="#000080"]; - - subgraph cluster_core { - style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; - label="Platform independent core"; - - Layout [URL="\ref dw::core::Layout"]; - Platform [URL="\ref dw::core::Platform", color="#ff8080"]; - View [URL="\ref dw::core::View", color="#ff8080"]; - Widget [URL="\ref dw::core::Widget", color="#a0a0a0"]; - } - - subgraph cluster_fltk { - style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; - label="FLTK specific part (as an\nexample for the platform specific\n\ -implementations)"; - - subgraph cluster_fltkcore { - style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; - label="FLTK core"; - - FltkPlatform [URL="\ref dw::fltk::FltkPlatform"]; - FltkView [URL="\ref dw::fltk::FltkView", color="#ff8080"]; - } - - FltkViewport [URL="\ref dw::fltk::FltkViewport"]; - FltkPreview [URL="\ref dw::fltk::FltkPreview"]; - } - - subgraph cluster_widgets { - style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; - label="Platform independent widgets"; - - Textblock [URL="\ref dw::Textblock"]; - AlignedTextblock [URL="\ref dw::AlignedTextblock", color="#a0a0a0"]; - Table [URL="\ref dw::Table"]; - Image [URL="\ref dw::Image"]; - etc1 [label="..."]; - etc2 [label="..."]; - } - - Layout -> Platform [headlabel="1", taillabel="1"]; - Layout -> View [headlabel="*", taillabel="1"]; - - Layout -> Widget [headlabel="1", taillabel="1", label="topLevel"]; - Widget -> Widget [headlabel="*", taillabel="1", label="children"]; - - Widget -> Textblock [arrowhead="none", arrowtail="empty", dir="both"]; - Widget -> Table [arrowhead="none", arrowtail="empty", dir="both"]; - Widget -> Image [arrowhead="none", arrowtail="empty", dir="both"]; - Widget -> etc1 [arrowhead="none", arrowtail="empty", dir="both"]; - Textblock -> AlignedTextblock [arrowhead="none", arrowtail="empty", - dir="both"]; - AlignedTextblock -> etc2 [arrowhead="none", arrowtail="empty", dir="both"]; - - Platform -> FltkPlatform [arrowhead="none", arrowtail="empty", dir="both", - style="dashed"]; - FltkPlatform -> FltkView [headlabel="*", taillabel="1"]; - - View -> FltkView [arrowhead="none", arrowtail="empty", dir="both"]; - FltkView -> FltkViewport [arrowhead="none", arrowtail="empty", dir="both", - style="dashed"]; - FltkView -> FltkPreview [arrowhead="none", arrowtail="empty", dir="both", - style="dashed"]; -} -\enddot - -
[\ref uml-legend "legend"]
- -\em Platform means in most cases the underlying UI toolkit -(e.g. FLTK). A layout is bound to a specific platform, but multiple -platforms may be handled in one program. - -A short overview: - -
    -
  • dw::core::Layout is the central class, it manages the widgets and the - view, and provides delegation methods for the platform. - -
  • The layouting is done by a tree of widgets (details are described in - \ref dw-layout-widgets), also the drawing, which is finally delegated - to the view. - -
  • The view (implementation of dw::core::View) provides primitive methods - for drawing, but also have an influence on - the canvas size (via size hints). See \ref dw-layout-views for details. - -
  • Some platform dependencies are handled by implementations - of dw::core::Platform. -
- - -

Header Files

- -The structures mentioned above can be found in the following header -files: - -
    -
  • Anything from the Dw core in core.hh. Do not include the single files. - -
  • The single widgets can be found in the respective header files, e.g. - image.hh for dw::Image. - -
  • The core of the FLTK implementation is defined in fltkcore.hh. This - includes dw::fltk::FltkPlatform, dw::fltk::FltkView, but not the concrete - view implementations. - -
  • The views can be found in single header files, e.g fltkviewport.hh for - dw::fltk::FltkViewport. -
- - -

Further Documentations

- -A complete map can be found at \ref dw-map. - -
    -
  • For learning, how to use Dw, read \ref dw-usage and related documents, - dw::core::style, dw::core::ui and \ref dw-images-and-backgrounds. -
  • Advanced topics are described in \ref dw-layout-widgets, - \ref dw-widget-sizes and \ref dw-layout-views. -
- -*/ diff --git a/doc/dw-size-of-widget.png b/doc/dw-size-of-widget.png deleted file mode 100644 index dbdbe0c4..00000000 Binary files a/doc/dw-size-of-widget.png and /dev/null differ diff --git a/doc/dw-style-box-model.png b/doc/dw-style-box-model.png deleted file mode 100644 index bf2fb1f1..00000000 Binary files a/doc/dw-style-box-model.png and /dev/null differ diff --git a/doc/dw-style-length-absolute.png b/doc/dw-style-length-absolute.png deleted file mode 100644 index 9ea28cad..00000000 Binary files a/doc/dw-style-length-absolute.png and /dev/null differ diff --git a/doc/dw-style-length-percentage.png b/doc/dw-style-length-percentage.png deleted file mode 100644 index b1ad79c9..00000000 Binary files a/doc/dw-style-length-percentage.png and /dev/null differ diff --git a/doc/dw-style-length-relative.png b/doc/dw-style-length-relative.png deleted file mode 100644 index ee79b1a9..00000000 Binary files a/doc/dw-style-length-relative.png and /dev/null differ diff --git a/doc/dw-textblock-collapsing-spaces-1-1.png b/doc/dw-textblock-collapsing-spaces-1-1.png deleted file mode 100644 index d528dfb2..00000000 Binary files a/doc/dw-textblock-collapsing-spaces-1-1.png and /dev/null differ diff --git a/doc/dw-textblock-collapsing-spaces-1-2.png b/doc/dw-textblock-collapsing-spaces-1-2.png deleted file mode 100644 index 483e79d1..00000000 Binary files a/doc/dw-textblock-collapsing-spaces-1-2.png and /dev/null differ diff --git a/doc/dw-textblock-collapsing-spaces-2-1.png b/doc/dw-textblock-collapsing-spaces-2-1.png deleted file mode 100644 index 0a03ea80..00000000 Binary files a/doc/dw-textblock-collapsing-spaces-2-1.png and /dev/null differ diff --git a/doc/dw-textblock-collapsing-spaces-2-2.png b/doc/dw-textblock-collapsing-spaces-2-2.png deleted file mode 100644 index b89c6254..00000000 Binary files a/doc/dw-textblock-collapsing-spaces-2-2.png and /dev/null differ diff --git a/doc/dw-usage.doc b/doc/dw-usage.doc deleted file mode 100644 index a23920b8..00000000 --- a/doc/dw-usage.doc +++ /dev/null @@ -1,375 +0,0 @@ -/** \page dw-usage Dillo Widget Usage - -This document describes the usage of Dw, without going too much into -detail. - - -

Getting Started

- -In this section, a small runnable example is described, based on the -FLTK implementation. - -As described in \ref dw-overview, the following objects are needed: - -
    -
  • dw::core::Layout, -
  • an implementation of dw::core::Platform (we will use - dw::fltk::FltkPlatform), -
  • at least one implementation of dw::core::View (dw::fltk::FltkViewport), - and -
  • some widgets (for this example, only a simple dw::Textblock). -
- -First of all, the necessary \#include's: - -\code -#include -#include - -#include "dw/core.hh" -#include "dw/fltkcore.hh" -#include "dw/fltkviewport.hh" -#include "dw/textblock.hh" -\endcode - -Everything is put into one function: - -\code -int main(int argc, char **argv) -{ -\endcode - -As the first object, the platform is instantiated: - -\code - dw::fltk::FltkPlatform *platform = new dw::fltk::FltkPlatform (); -\endcode - -Then, the layout is created, with the platform attached: - -\code - dw::core::Layout *layout = new dw::core::Layout (platform); -\endcode - -For the view, we first need a FLTK window: - -\code - Fl_Window *window = new Fl_Window(200, 300, "Dw Example"); - window->begin(); -\endcode - -After this, we can create a viewport, and attach it to the layout: - -\code - dw::fltk::FltkViewport *viewport = - new dw::fltk::FltkViewport (0, 0, 200, 300); - layout->attachView (viewport); -\endcode - -Each widget needs a style (dw::core::style::Style, see dw::core::style), -so we construct it here. For this, we need to fill a -dw::core::style::StyleAttrs structure with values, and call -dw::core::style::Style::create (latter is done further below): - -\code - dw::core::style::StyleAttrs styleAttrs; - styleAttrs.initValues (); - styleAttrs.margin.setVal (5); -\endcode - -dw::core::style::StyleAttrs::initValues sets several default -values. The last line sets a margin of 5 pixels. Next, we need a -font. Fonts are created in a similar way, first, the attributes are -defined: - -\code - dw::core::style::FontAttrs fontAttrs; - fontAttrs.name = "Bitstream Charter"; - fontAttrs.size = 14; - fontAttrs.weight = 400; - fontAttrs.style = dw::core::style::FONT_STYLE_NORMAL; - fontAttrs.letterSpacing = 0; - fontAttrs.fontVariant = dw::core::style::FONT_VARIANT_NORMAL; -\endcode - -Now, the font can be created: - -\code - styleAttrs.font = dw::core::style::Font::create (layout, &fontAttrs); -\endcode - -As the last attributes, the background and forground colors are -defined, here dw::core::style::Color::createSimple must be called: - -\code - styleAttrs.color = - dw::core::style::Color::create (layout, 0x000000); - styleAttrs.backgroundColor = - dw::core::style::Color::create (layout, 0xffffff); -\endcode - -Finally, the style for the widget is created: - -\code - dw::core::style::Style *widgetStyle = - dw::core::style::Style::create (layout, &styleAttrs); -\endcode - -Now, we create a widget, assign a style to it, and set it as the -toplevel widget of the layout: - -\code - dw::Textblock *textblock = new dw::Textblock (false); - textblock->setStyle (widgetStyle); - layout->setWidget (textblock); -\endcode - -The style is not needed anymore (a reference is added in -dw::core::Widget::setStyle), so it should be unreferred: - -\code - widgetStyle->unref(); -\endcode - -Now, some text should be added to the textblock. For this, we first -need another style. \em styleAttrs can still be used for this. We set -the margin to 0, and the background color to "transparent": - -\code - styleAttrs.margin.setVal (0); - styleAttrs.backgroundColor = NULL; - - dw::core::style::Style *wordStyle = - dw::core::style::Style::create (layout, &styleAttrs); -\endcode - -This loop adds some paragraphs: - -\code - for(int i = 1; i <= 10; i++) { - char buf[4]; - sprintf(buf, "%d.", i); - - char *words[] = { "This", "is", "the", buf, "paragraph.", - "Here", "comes", "some", "more", "text", - "to", "demonstrate", "word", "wrapping.", - NULL }; - - for(int j = 0; words[j]; j++) { - textblock->addText(strdup(words[j]), wordStyle); -\endcode - -Notice the \em strdup, dw::Textblock::addText will feel responsible -for the string, and free the text at the end. (This has been done to -avoid some overhead in the HTML parser.) - -The rest is simple, it also includes spaces (which also have styles): - -\code - textblock->addSpace(wordStyle); - } -\endcode - -Finally, a paragraph break is added, which is 10 pixels high: - -\code - textblock->addParbreak(10, wordStyle); - } -\endcode - -Again, this style should be unreferred: - -\code - wordStyle->unref(); -\endcode - -After adding text, this method should always be called (for faster -adding large text blocks): - -\code - textblock->flush (); -\endcode - -Some FLTK stuff to finally show the window: - -\code - window->resizable(viewport); - window->show(); - int errorCode = Fl::run(); -\endcode - -For cleaning up, it is sufficient to destroy the layout: - -\code - delete layout; -\endcode - -And the rest - -\code - return errorCode; -} -\endcode - -If you compile and start the program, you should see the following: - -\image html dw-example-screenshot.png - -Try to scroll, or to resize the window, you will see, that everything -is done automatically. - -Of course, creating new widgets, adding text to widgets etc. can also -be done while the program is running, i.e. after fltk::run has been -called, within timeouts, idles, I/O functions etc. Notice that Dw is -not thread safe, so that everything should be done within one thread. - -With the exception, that you have to call dw::Textblock::flush, -everything gets immediately visible, within reasonable times; Dw has -been optimized for frequent updates. - - -

List of all Widgets

- -These widgets are used within dillo: - -
    -
  • dw::core::ui::Embed -
  • dw::AlignedTextblock -
  • dw::Bullet -
  • dw::Ruler -
  • dw::Image -
  • dw::ListItem -
  • dw::Table -
  • dw::TableCell -
  • dw::Textblock -
- -If you want to create a new widget, refer to \ref dw-layout-widgets. - - -

List of Views

- -There are three dw::core::View implementations for FLTK: - -
    -
  • dw::fltk::FltkViewport implements a viewport, which is used in the - example above. - -
  • dw::fltk::FltkPreview implements a preview window, together with - dw::fltk::FltkPreviewButton, it is possible to have a scaled down - overview of the whole canvas. - -
  • dw::fltk::FltkFlatView is a "flat" view, i.e. it does not support - scrolling. It is used for HTML buttons, see - dw::fltk::ui::FltkComplexButtonResource and especially - dw::fltk::ui::FltkComplexButtonResource::createNewWidget for details. -
- -More informations about views in general can be found in \ref -dw-layout-views. - - -

Iterators

- -For examining generally the contents of widgets, there are iterators -(dw::core::Iterator), created by the method -dw::core::Widget::iterator (see there for more details). - -These simple iterators only iterate through one widget, and return -child widgets as dw::core::Content::WIDGET. The next call of -dw::core::Iterator::next will return the piece of contents \em after -(not within) this child widget. - -If you want to iterate through the whole widget trees, there are two -possibilities: - -
    -
  1. Use a recursive function. Of course, with this approach, you are - limited by the program flow. - -
  2. Maintain a stack of iterators, so you can freely pass this stack - around. This is already implemented, as dw::core::DeepIterator. -
- -As an example, dw::core::SelectionState represents the selected region -as two instances of dw::core::DeepIterator. - - -

Finding Text

- -See dw::core::Layout::findtextState and dw::core::FindtextState -(details in the latter). There are delegation methods: - -
    -
  • dw::core::Layout::search and -
  • dw::core::Layout::resetSearch. -
- - -

Anchors and Scrolling

- -In some cases, it is necessary to scroll to a given position, or to -an anchor, programmatically. - -

Anchors

- -Anchors are defined by widgets, e.g. dw::Textblock defines them, when -dw::Textblock::addAnchor is called. To jump to a specific anchor -within the current widget tree, use dw::core::Layout::setAnchor. - -This can be done immediately after assignig a toplevel widget, even -when the anchor has not yet been defined. The layout will remember the -anchor, and jump to the respective position, as soon as possible. Even -if the anchor position changes (e.g., when an anchor is moved -downwards, since some space is needed for an image in the text above), -the position is corrected. - -As soon as the user scrolls the viewport, this correction is not done -anymore. If in dillo, the user request a page with an anchor, which is -quite at the bottom of the page, he may be get interested in the text -at the beginning of the page, and so scrolling down. If then, after -the anchor has been read and added to the dw::Textblock, this anchor -would be jumped at, the user would become confused. - -The anchor is dismissed, too, when the toplevel widget is removed -again. - -\todo Currently, anchors only define vertical positions. - -

Scrolling

- -To scroll to a given position, use the method -dw::core::Layout::scrollTo. It expects several parameters: - -
    -
  • a horizontal adjustment parameter, defined by dw::core::HPosition, -
  • a vertical adjustment parameter, defined by dw::core::VPosition, and -
  • a rectangle (\em x, \em y, \em width and \em heigh) of the region - to be adjusted. -
- -If you just want to move the canvas coordinate (\em x, \em y) into the -upper left corner of the viewport, you can call: - -\code -dw::core::Layout *layout; -// ... -layout->scrollTo(dw::core::HPOS_LEFT, dw::core::VPOS_TOP, 0, 0, 0, 0); -\endcode - -By using dw::core::HPOS_NO_CHANGE or dw::core::VPOS_NO_CHANGE, you can -change only one dimension. dw::core::HPOS_INTO_VIEW and -dw::core::VPOS_INTO_VIEW will cause the viewport to move as much as -necessary, that the region is visible in the viewport (this is -e.g. used for finding text). - - -

Further Documentations

- -
    -
  • dw::core::style -
  • dw::core::ui -
  • \ref dw-images-and-backgrounds -
- -*/ diff --git a/doc/dw-viewport-with-scrollbar.png b/doc/dw-viewport-with-scrollbar.png deleted file mode 100644 index 7ac62de3..00000000 Binary files a/doc/dw-viewport-with-scrollbar.png and /dev/null differ diff --git a/doc/dw-viewport-without-scrollbar.png b/doc/dw-viewport-without-scrollbar.png deleted file mode 100644 index 8aa20fec..00000000 Binary files a/doc/dw-viewport-without-scrollbar.png and /dev/null differ diff --git a/doc/dw-widget-sizes.doc b/doc/dw-widget-sizes.doc deleted file mode 100644 index a82d3b99..00000000 --- a/doc/dw-widget-sizes.doc +++ /dev/null @@ -1,277 +0,0 @@ -/** \page dw-widget-sizes Sizes of Dillo Widgets - -
Info: -Not up to date, see \ref dw-grows.
- -Allocation -========== - -Each widget has an \em allocation at a given time, this includes - -- the position (\em x, \em y) relative to the upper left corner of the - canvas, and -- the size (\em width, \em ascent, \em descent). - -The \em canvas is the whole area available for the widgets, in most -cases, only a part is seen in a viewport. The allocation of the -toplevel widget is exactly the allocation of the canvas, i.e. - -- the position of the toplevel widget is always (0, 0), and -- the canvas size is defined by the size of the toplevel widget. - -The size of a widget is not simply defined by the width and the -height, instead, widgets may have a base line, and so are vertically -divided into an ascender (which height is called \em ascent), and a -descender (which height is called \em descent). The total height is so -the sum of \em ascent and \em descent. - -Sizes of zero are allowed. The upper limit for the size of a widget is -defined by the limits of the C++ type \em int. - -\image html dw-size-of-widget.png Allocation of a Widget - -In the example in the image, the widget has the following allocation: - -- \em x = 50 -- \em y = 50 -- \em width = 150 -- \em ascent = 150 -- \em descent = 100 - -The current allocation of a widget is hold in -dw::core::Widget::allocation. It can be set from outside by -calling dw::core::Widget::sizeAllocate. This is a concrete method, -which will call dw::core::Widget::sizeAllocateImpl (see code of -dw::core::Widget::sizeAllocate for details). - -For trivial widgets (like dw::Bullet), -dw::core::Widget::sizeAllocateImpl does not need to be -implemented. For more complex widgets, the implementation should call -dw::core::Widget::sizeAllocate (not -dw::core::Widget::sizeAllocateImpl) on all child widgets, with -appropriate child allocations. dw::core::Widget::allocation should not -be changed here, this is already done in -dw::core::Widget::sizeAllocate. - - -Requisitions -============ - -A widget may prefer a given size for the allocation. This size, the -\em requisition, should be returned by the method -dw::core::Widget::sizeRequestImpl. In the simplest case, this is -independent of the context, e.g. for an -image. dw::Image::sizeRequestImpl returns the following size: - -- If no buffer has yet been assigned (see dw::Image for more details), - the size necessary for the alternative text is returned. If no - alternative text has been set, zero is returned. - -- If a buffer has been assigned (by dw::Image::setBuffer), the root - size is returned (i.e. the original size of the image to display). - -This is a bit simplified, dw::Image::sizeRequestImpl should also deal -with margins, borders and paddings, see dw::core::style. - -From the outside, dw::Image::sizeRequest should be called, which does -a bit of optimization. Notice that in dw::Image::sizeRequestImpl, no -optimization like lazy evaluation is necessary, this is already done -in dw::Image::sizeRequest. - -A widget, which has children, will likely call dw::Image::sizeRequest -on its children, to calculate the total requisition. - -The caller (this is either the dw::core::Layout, or the parent -widget), may, but also may not consider the requisition. Instead, a -widget must deal with any allocation. (For example, dw::Image scales -the image buffer when allocated at another size.) - - -Size Hints -========== - -
Info: -Size hints have been removed, see \ref dw-grows.
- - -Width Extremes -============== - -dw::Table uses width extremes for fast calculation of column -widths. The structure dw::core::Extremes represents the minimal and -maximal width of a widget, as defined by: - -- the minimal width is the smallest width, at which a widget can still - display contents, and -- the maximal width is the largest width, above which increasing the - width- does not make any sense. - -Especially the latter is vaguely defined, here are some examples: - -- For those widgets, which do not depend on size hints, the minimal - and the maximal width is the inherent width (the one returned by - dw::core::Widget::sizeRequest). - -- For a textblock, the minimal width is the width of the widest - (unbreakable) word, the maximal width is the width of the total - paragraph (stretching a paragraph further would only waste space). - Actually, the implementation of dw::Textblock::getExtremesImpl is a - bit more complex. - -- dw::Table is an example, where the width extremes are calculated - from the width extremes of the children. - -Handling width extremes is similar to handling requisitions, a widget -must implement dw::core::Widget::getExtremesImpl, but a caller will -use dw::core::Widget::getExtremes. - - -Resizing -======== - -When the widget changes its size (requisition), it should call -dw::core::Widget::queueResize. The next call of -dw::core::Widget::sizeRequestImpl should then return the new -size. See dw::Image::setBuffer as an example. - -Interna are described in the code of dw::core::Widget::queueResize. - -

Incremental Resizing

- -A widget may calculate its size based on size calculations already -done before. In this case, a widget must exactly know the reasons, why -a call of dw::core::Widget::sizeRequestImpl is necessary. To make use -of this, a widget must implement the following: - -1. There is a member dw::core::Widget::parentRef, which is totally - under control of the parent widget (and so sometimes not used at - all). It is necessary to define how parentRef is used by a specific - parent widget, and it has to be set to the correct value whenever - necessary. -2. The widget must implement dw::core::Widget::markSizeChange and - dw::core::Widget::markExtremesChange, these methods are called in - two cases: - 1. directly after dw::core::Widget::queueResize, with the - argument ref was passed to dw::core::Widget::queueResize, - and - 2. if a child widget has called dw::core::Widget::queueResize, - with the value of the parent_ref member of this child. - -This way, a widget can exactly keep track on size changes, and so -implement resizing in a faster way. A good example on how to use this -is dw::Textblock. - - -Rules for Methods Related to Resizing -===================================== - -Which method can be called, when the call of another method is not -finished? These rules are important in two circumstances: - -1. To know which method can be called, and, especially, which methods - *must not* be called, within the implementation of - *sizeRequestImpl* (called by *sizeRequest*), *markSizeChange*, and - *markExtremesChange* (the latter two are called by *queueResize*). -2. On the other hand, to make sure that the calls, which are allowed, - are handled correctly, especially in implementations of - *sizeRequestImpl*, *markSizeChange*, *markExtremesChange* - -Generally, the rules defined below are, in case of doubt, rather -strict; when changing the rules, loosening is simpler than to tighten -them, since this will make it neccessary to review old code for calls -previously allowed but now forbidden. - -Short recap: - -- *QueueResize* directly calls *markSizeChange* and - *markExtremesChanges*, and queues an idle function for the actual - resizing (dw::core::Layout::resizeIdle). (The idle function is - called some time after *queueResize* is finished.) -- The resize idle function first calls *sizeRequest*, then - *sizeAllocate*, for the toplevel widget. - -In the following table, the rules are defined in detail. "Within call -of ..." includes all methods called from the original method: the -first row (*queueResize*) defines also the rules for -*markExtremesChanges* and *markExtremesChanges*, and in the second row -(*sizeAllocate*), even *sizeRequest* has to be considered. - -
Info: -Not up to date: *queueResize* can now be called recursively (so to -speak). See code there.
- - - - - - - - -
Within call of ... ↓ - ... is call allowed of ... ? → - queueResize - sizeAllocate - sizeRequest - getExtremes -
queueResize - No - No1 - No1 - No1 -
sizeAllocate - Yes - Only for children2 - Yes(?) - Yes(?) -
sizeRequest - Yes3 - No - Limited4 - Limited4 -
getExtremes - Yes3 - No - Limited4 - Limited4 -
1) Otherwise, since these other methods -may be call *queueResize*, the limitation that *queueResize* must not -call *queueResize* can be violated. - -2) Could perhaps be loosened as for *sizeRequest* and -*getExtremes*, but there is probably no need. - -3) Therefore the distinction between *RESIZE_QUEUED* and -*NEEDS_RESIZE*, and *EXTREMES_QUEUED* and *EXTREMES_CHANGED*, -respectively. - -4) Calls only for children are safe. In other cases, you -take a large responsibility to prevent endless recursions by -(typically indirectly) calling *sizeRequest* / *getExtremes* for -direct ancestors. -
- -Furthermore, *sizeAllocate* can only be called within a call of -dw::core::Layout::resizeIdleId, so (if you do not touch dw::core) do -not call it outside of *sizeAllocateImpl*. The other methods can be -called outsize; e. g. *sizeRequest* is called in -dw::Textblock::addWidget. - -To avoid painful debugging, there are some tests for the cases that -one method call is strictly forbidden while another method is called. - -This could be done furthermore: - -- The tests could be refined. -- Is it possible to define exacter rules, along with a proof that no - problems (like endless recursion) can occur? - - -See also -======== - -- \ref dw-grows - -*/ diff --git a/doc/fltk-problems.doc b/doc/fltk-problems.doc deleted file mode 100644 index df4f1f14..00000000 --- a/doc/fltk-problems.doc +++ /dev/null @@ -1,180 +0,0 @@ -/** \page fltk-problems Problems with FLTK - -

dw::fltk::FltkViewport

- -Current problems: - -
    -
  • How should dw::fltk::FltkViewport::cancelQueueDraw be implemented? - -
  • If the value of a scrollbar is changed by the program, not the user, - the callback seems not to be called. Can this be assured? - -
  • The same for dw::fltk::FltkViewport::layout? - -
  • Also, the problems with the widgets seems to work. Also sure? - -
  • When drawing, clipping of 32 bit values is not working properly. - -
  • The item group within a selection widget (menu) should not be selectable. -
- - -

dw::fltk::FltkPlatform

- -
    -
  • There is the problem, that fltk::font always returns a font, the - required one, or a replacements. The latter is not wanted in all - cases, e.g. when several fonts are tested. Perhaps, this could be - solved by searching in the font list. [This was true of fltk2. - What is the state of font handling now with fltk-1.3?] - -
  • Distinction between italics and oblique would be nice - (dw::fltk::FltkFont::FltkFont). -
- - -

dw::fltk::ui::FltkCheckButtonResource

- -Groups of Fl_Radio_Button must be added to one Fl_Group, which is -not possible in this context. There are two alternatives: - -
    -
  1. there is a more flexible way to group radio buttons, or -
  2. radio buttons are not grouped, instead, grouping (especially - unchecking other buttons) is done by the application. -
- -(This is mostly solved.) - -

dw::fltk::FltkImgbuf

- -Alpha transparency should be best abstracted by FLTK itself. If not, -perhaps different implementations for different window systems could -be used. Then, it is for X necessary to use GCs with clipping masks. - - -

dw::fltk::ui::ComplexButton

- -Unfortunately, FLTK does not provide a button with Fl_Group as parent, so -that children may be added to the button. dw::fltk::ui::ComplexButton does -exactly this, and is, in an ugly way, a modified copy of the FLTK -button. - -It would be nice, if this is merged with the standard FLTK -button. Furthermore, setting the type is strange. - -If the files do not compile, it may be useful to create a new one from -the FLTK source: - -
    -
  1. Copy Fl_Button.H from FLTK to dw/fltkcomplexbutton.hh and - src/Button.cxx to dw/fltkcomplexbutton.cc. - -
  2. In both files, rename "Button" to "ComplexButton". Automatic replacing - should work. - -
  3. Apply the changes below. -
- -The following changes should be applied manually. - -

Changes in fltkcomplexbutton.hh

- -First of all, the \#define's for avoiding multiple includes: - -\code --#ifndef fltk_ComplexButton_h // fltk_Button_h formerly --#define fltk_ComplexButton_h -+#ifndef __FLTK_COMPLEX_BUTTON_HH__ -+#define __FLTK_COMPLEX_BUTTON_HH__ -\endcode - -at the beginning and - -\code --#endif -+#endif // __FLTK_COMPLEX_BUTTON_HH__ -\endcode - -at the end. Then, the namespace is changed: - -\code --namespace fltk { -+namespace dw { -+namespace fltk { -+namespace ui { -\endcode - -at the beginning and - -\code --} -+} // namespace ui -+} // namespace fltk -+} // namespace dw -\endcode - -at the end. Most important, the base class is changed: - -\code --#include "FL/Fl_Widget.H" -+#include -\endcode - -and - -\code --class FL_API ComplexButton : public Fl_Widget { -+class ComplexButton: public Fl_Group -+{ -\endcode - -Finally, for dw::fltk::ui::ComplexButton::default_style, there is a -namespace conflict: - -\code -- static NamedStyle* default_style; -+ static ::fltk::NamedStyle* default_style; -\endcode - -

Changes in fltkcomplexbutton.cc

- -First, \#include's: - -\code - - #include --#include // formerly - #include - #include -+ -+#include "fltkcomplexbutton.hh" -\endcode - -Second, namespaces: - -\code -+using namespace dw::fltk::ui; -\endcode - -Since the base class is now Fl_Group, the constructor must be changed: - -\code --ComplexButton::ComplexButton(int x,int y,int w,int h, const char *l) : Fl_Widget(x,y,w,h,l) { -+ComplexButton::ComplexButton(int x,int y,int w,int h, const char *l) : -+ Fl_Group(x,y,w,h,l) -+{ -\endcode - -Finally, the button must draw its children (end of -dw::fltk::ui::ComplexButton::draw()): - -\code -+ -+ for (int i = children () - 1; i >= 0; i--) -+ draw_child (*child (i)); - } -\endcode - -*/ diff --git a/doc/index.doc b/doc/index.doc deleted file mode 100644 index 59de8cd8..00000000 --- a/doc/index.doc +++ /dev/null @@ -1,48 +0,0 @@ -/** \mainpage - -

Overview

- -This is a list of documents to start with: - -
    -
  • \ref lout -
  • \ref dw-overview (map at \ref dw-map) -
- -Currently, a document \ref fltk-problems is maintained, ideally, it -will be removed soon. - -

Historical

- -

Replacements for GTK+ and GLib

- -There are several classes etc., which are used for tasks formerly (in the GTK+ -version of dillo) achieved by GtkObject (in 1.2.x, this is part of Gtk+) and -GLib. For an overview on all this, take a look at \ref lout. - -GtkObject is replaced by the following: - -
    -
  • lout::object::Object is a common base class for many classes used - dillo. In the namespace lout::object, there are also some more common - classes and interfaces. - -
  • A sub class of lout::object::Object is - lout::identity::IdentifiableObject, which allows to determine the - class at run-time (equivalent to GTK_CHECK_CAST in GtkObject). - -
  • For signals, there is the namespace lout::signal. -
- -Hash tables, linked lists etc. can be found in the lout::container namespace, -several useful macros from GLib have been implemented as inline functions -in the lout::misc namespace. - -As an alternative to the macros defined in list.h, there is also a template -class, lout::misc::SimpleVector, which does the same. - -

Changes in Dw

- -If you have been familiar with Dw before, take a look at \ref dw-changes. - -*/ diff --git a/doc/lout.doc b/doc/lout.doc deleted file mode 100644 index 4e1503c6..00000000 --- a/doc/lout.doc +++ /dev/null @@ -1,95 +0,0 @@ -/** \page lout Lots of Useful Tools - -In the "lout" directory, there are some common base functionality for -C++. Most is described as doxygen comments, this text gives an -overview. - -

Common Base Class

- -Many classes are derived from lout::object::Object, which defines some -general methods. See there for more information. - -For the case, that you need primitive C++ types, there are some -wrappers: - - -
C++ Type Wrapper Class -
void* lout::object::Pointer -
specific pointer lout::object::TypedPointer (template class) -
int lout::object::Integer -
const char* lout::object::ConstString -
char* lout::object::String -
- - -

Containers

- -In the namespace lout::container, several container classes are defined, -which all deal with instances of lout::object::Object. - -

Untyped Containers

- -In lout::container::untyped, there are the following containers: - -
    -
  • lout::container::untyped::Vector, a dynamically increases array, -
  • lout::container::untyped::List, a linked list, -
  • lout::container::untyped::HashTable, a hash table, and -
  • lout::container::untyped::Stack, a stack. -
- -All provide specific methods, but since they have a common base class, -lout::container::untyped::Collection, they all provide iterators, by the -method lout::container::untyped::Collection::iterator. - -

Typed Containers

- -lout::container::typed provides wrappers for the container classes defined -in lout::container::untyped, which are more type safe, by using C++ -templates. - - -

Signals

- -For how to connect objects at run-time (to reduce dependencies), take a -look at the lout::signal namespace. - -There is also a base class lout::signal::ObservedObject, which implements -signals for deletion. - - -

Debugging

- -In debug.hh, there are some some useful macros for debugging messages, -see the file for mor informations. - - -

Identifying Classes at Runtime

- -If the class of an object must be identified at runtime, -lout::identity::IdentifiableObject should be used as the base class, -see there for more details. - - -

Miscellaneous

- -The lout::misc namespace provides several miscellaneous stuff: - -
    -
  • In some contexts, it is necessary to compare objects - (less/greater), for this, also lout::misc::Comparable must be - implemented. For example., lout::container::untyped::Vector::sort and - lout::container::typed::Vector::sort cast the elements to - lout::misc::Comparable. This can be mixed with lout::object::Object. -
  • lout::misc::SimpleVector, a simple, template based vector class - (not depending on lout::object::Object) (a variant for handling a - special case in an efficient way is lout::misc::NotSoSimpleVector), -
  • lout::misc::StringBuffer, class for fast concatenation of a large number - of strings, -
  • lout::misc::BitSet implements a bitset. -
  • useful (template) functions (lout::misc::min, lout::misc::max), and -
  • some functions useful for runtime checks (lout::misc::assert, - lout::misc::assertNotReached). -
- -*/ diff --git a/doc/not-so-simple-container.png b/doc/not-so-simple-container.png deleted file mode 100644 index 0af067b5..00000000 Binary files a/doc/not-so-simple-container.png and /dev/null differ diff --git a/doc/rounding-errors.doc b/doc/rounding-errors.doc deleted file mode 100644 index a442033e..00000000 --- a/doc/rounding-errors.doc +++ /dev/null @@ -1,35 +0,0 @@ -/** \page rounding-errors How to Avoid Rounding Errors - -(Probably, this is a standard algorithm, so if someone knows the name, -drop me a note.) - -If something like - -\f[y_i = {x_i a \over b}\f] - -is to be calculated, and all numbers are integers, a naive -implementation would result in something, for which - -\f[\sum y_i \ne {(\sum x_i) a \over b}\f] - -because of rounding errors, due to the integer division. This can be -avoided by transforming the formula into - -\f[y_i = {(\sum_{j=0}^{j=i} x_j) a \over b} - \sum_{j=0}^{j=i-1} y_j\f] - -Of corse, when all \f$y_i\f$ are calculated in a sequence, -\f$\sum_{j=0}^{j=i} x_j\f$ and \f$\sum_{j=0}^{j=i-1} y_j\f$ can be -accumulated in the same loop. Regard this as sample: - -\code -int n, x[n], a, b; // Should all be initialized. -int y[n], cumX = 0, cumY = 0; - -for (int i = 0; i < n; i++) { - cumX += x[i] - y[i] = (cumX * a) / b - cumY; - cumY += y[i]; -} -\endcode - -*/ diff --git a/doc/uml-legend.doc b/doc/uml-legend.doc deleted file mode 100644 index 54004ccd..00000000 --- a/doc/uml-legend.doc +++ /dev/null @@ -1,195 +0,0 @@ -/** \page uml-legend UML Legend - -This page describes the notation for several diagrams used in the -documentation, which is a slight variation of UML. - - -

Classes

- -Classes are represented by boxes, containing there names: - -\dot -digraph G { - node [shape=record, fontname=Helvetica, fontsize=10]; - fontname=Helvetica; fontsize=8; - "Concrete Class"; - "Abstract Class" [color="#a0a0a0"]; - Interface [color="#ff8080"]; -} -\enddot - -(In most cases, the attributes and operations are left away, for -better readibility. Just click on it, to get to the detailed -description.) - -Of course, in C++, there are no interfaces, but here, we call a class, -which has only virtual abstract methods, and so does not provide any -functionality, an interface. - -Templates get a yellow background color: - -\dot -digraph G { - node [shape=record, fontname=Helvetica, fontsize=10, - fillcolor="#ffffc0", style="filled"]; - fontname=Helvetica; fontsize=8; - "Concrete Class Template"; - "Abstract Class Template" [color="#a0a0a0"]; - "Interface Template" [color="#ff8080"]; -} -\enddot - - -

Objects

- -In some cases, an examle for a concrete constellation of objects is -shown. An object is represented by a box containing a name and the -class, separated by a colon. - -\dot -digraph G { - node [shape=record, fontname=Helvetica, fontsize=10]; - edge [arrowhead="open", labelfontname=Helvetica, labelfontsize=10, - color="#404040", labelfontcolor="#000080"]; - fontname=Helvetica; fontsize=10; - - "x: A" -> "y1: B"; - "x: A" -> "y2: B"; -} -\enddot - -The names (\em x, \em y, and \em z) are only meant within the context -of the diagram, there needs not to be a relation to the actual names -in the program. They should be unique within the diagram. - -Classes and objects may be mixed in one diagram. - - -

Associations

- -\dot -digraph G { - node [shape=record, fontname=Helvetica, fontsize=10]; - edge [arrowhead="open", labelfontname=Helvetica, labelfontsize=10, - color="#404040", labelfontcolor="#000080", - fontname=Helvetica, fontsize=10, fontcolor="#000080"]; - fontname=Helvetica; fontsize=10; - A -> B [headlabel="*", taillabel="1", label="x"]; -} -\enddot - -In this example, one instance of A refers to an arbitrary number of B -instances (denoted by the "*"), and each instance of B is referred by -exactly one ("1") A. The label \em x is the name of the association, -in most cases the name of the field, e.g. A::x. - -Possible other values for the \em multiplicity: - -
    -
  • a concrete number, in most cases "1", -
  • a range, e.g. "0..1", -
  • "*", denoting an arbitrary number. -
- - -

Implementations and Inheritance

- -\dot -digraph G { - node [shape=record, fontname=Helvetica, fontsize=10]; - edge [arrowhead="none", dir="both", arrowtail="empty", - labelfontname=Helvetica, labelfontsize=10, color="#404040", - labelfontcolor="#000080"]; - fontname=Helvetica; fontsize=10; - A[color="#ff8080"]; - B[color="#ff8080"]; - C; - D; - A -> B; - A -> C [style="dashed"]; - C -> D; -} -\enddot - -In this example, - -
    -
  • the interface B extends the interface A, -
  • the class C implements the interface A, and -
  • the class D extends the class C. -
- - -

Template Instantiations

- -Template instantiations are shown as own classes/interfaces, the -instantiation by the template is shown by a yellow dashed arrow: - -\dot -digraph G { - node [shape=record, fontname=Helvetica, fontsize=10]; - edge [arrowhead="none", arrowtail="empty", dir="both", - labelfontname=Helvetica, labelfontsize=10, color="#404040", - labelfontcolor="#000080"]; - fontname=Helvetica; fontsize=10; - - A[color="#ff8080"]; - B[color="#ff8080"]; - C[color="#ff8080", fillcolor="#ffffc0", style="filled"]; - C_A[color="#ff8080", label="C \"]; - C_B[color="#ff8080", label="C \"]; - D; - - C -> C_A [arrowhead="open", arrowtail="none", style="dashed", - color="#808000"]; - C -> C_B [arrowhead="open", arrowtail="none", style="dashed", - color="#808000"]; - A -> C_A; - B -> C_B; - C_A -> D [style="dashed"]; -} -\enddot - -In this example, the interface template C uses the template argument -as super interface. - - -

Packages

- -Packages are presented by dashed rectangles: - -\dot -digraph G { - node [shape=record, fontname=Helvetica, fontsize=10]; - edge [arrowhead="none", arrowtail="empty", dir="both", - labelfontname=Helvetica, labelfontsize=10, color="#404040", - labelfontcolor="#000080"]; - fontname=Helvetica; fontsize=10; - - subgraph cluster_1 { - style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; - label="package 1"; - - A; - B [color="#a0a0a0"]; - } - - subgraph cluster_2 { - style="dashed"; color="#000080"; fontname=Helvetica; fontsize=10; - label="package 2"; - - C; - D [color="#a0a0a0"]; - E - } - - A -> C; - B -> D; - D -> E; - E -> A [arrowhead="open", arrowtail="none"]; -} -\enddot - -Packages may be nested. - -*/ \ No newline at end of file -- cgit v1.2.3 From 77e49d91f226b5a4bde86bd52fb7793665906992 Mon Sep 17 00:00:00 2001 From: Sebastian Geerken Date: Mon, 1 Jun 2015 22:23:47 +0200 Subject: NotSoSimpleVector: nicer image --- devdoc/not-so-simple-container.png | Bin 5738 -> 19319 bytes devdoc/not-so-simple-container.svg | 785 +++++++++++++++++++++++++++++++++++++ 2 files changed, 785 insertions(+) create mode 100644 devdoc/not-so-simple-container.svg diff --git a/devdoc/not-so-simple-container.png b/devdoc/not-so-simple-container.png index 0af067b5..f3e2c039 100644 Binary files a/devdoc/not-so-simple-container.png and b/devdoc/not-so-simple-container.png differ diff --git a/devdoc/not-so-simple-container.svg b/devdoc/not-so-simple-container.svg new file mode 100644 index 00000000..ce00510e --- /dev/null +++ b/devdoc/not-so-simple-container.svg @@ -0,0 +1,785 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + unaffected (in main array) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + main array (moved) + original extra array + new inserted area + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Example 1: + + Example 2: + + -- cgit v1.2.3 From e9e3d1e1c27dab1b3abcc84aee687fa4a74bff64 Mon Sep 17 00:00:00 2001 From: corvid Date: Mon, 1 Jun 2015 20:35:23 +0000 Subject: dillo man page date --- doc/dillo.1.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/dillo.1.in b/doc/dillo.1.in index f86d050c..0853e6eb 100644 --- a/doc/dillo.1.in +++ b/doc/dillo.1.in @@ -1,4 +1,4 @@ -.TH dillo 1 "December 20, 2014" "" "USER COMMANDS" +.TH dillo 1 "May 28, 2015" "" "USER COMMANDS" .SH NAME dillo \- web browser .SH SYNOPSIS -- cgit v1.2.3 From c418194ce5e99ded90fb963bb268ac64552d4c6e Mon Sep 17 00:00:00 2001 From: Jeremy Henty Date: Tue, 2 Jun 2015 21:06:36 +0200 Subject: Remove dw::Table::_unused_calcColumnExtremes(). --- dw/table.cc | 18 ------------------ dw/table.hh | 1 - 2 files changed, 19 deletions(-) diff --git a/dw/table.cc b/dw/table.cc index 4ceb4020..64b7259e 100644 --- a/dw/table.cc +++ b/dw/table.cc @@ -1208,24 +1208,6 @@ void Table::apportionRowSpan () } -/** - * \brief Fills dw::Table::colExtremes, only if recalculation is necessary. - * - * \bug Some parts are missing. - */ -void Table::_unused_calcColumnExtremes () -{ - // This method is actually not used. Consider removal. - - DBG_OBJ_ENTER0 ("resize", 0, "calcColumnExtremes"); - - if (extremesChanged () || extremesQueued ()) - forceCalcColumnExtremes (); - - DBG_OBJ_LEAVE (); -} - - /** * \brief Fills dw::Table::colExtremes in all cases. */ diff --git a/dw/table.hh b/dw/table.hh index ed433594..e1764dbb 100644 --- a/dw/table.hh +++ b/dw/table.hh @@ -440,7 +440,6 @@ private: void actuallyCalcCellSizes (bool calcHeights); void apportionRowSpan (); - void _unused_calcColumnExtremes (); void forceCalcColumnExtremes (); void calcExtremesSpanMultiCols (int col, int cs, core::Extremes *cellExtremes, -- cgit v1.2.3 From 7ee8d351b79542b121484390c59428ef4a7ef657 Mon Sep 17 00:00:00 2001 From: "Jeremy Henty, Sebastian Geerken" Date: Tue, 2 Jun 2015 22:06:50 +0200 Subject: Updated dw::Table documentation. --- dw/table.cc | 2 +- dw/table.hh | 31 +++++++++++++++++-------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/dw/table.cc b/dw/table.cc index 64b7259e..d55fd72f 100644 --- a/dw/table.cc +++ b/dw/table.cc @@ -831,7 +831,7 @@ void Table::actuallyCalcCellSizes (bool calcHeights) int childHeight; core::Extremes extremes; - // Will also call calcColumnExtremes(), when needed. + // Will also call forceCalcColumnExtremes(), when needed. getExtremes (&extremes); int availWidth = getAvailWidth (true); diff --git a/dw/table.hh b/dw/table.hh index e1764dbb..9b868642 100644 --- a/dw/table.hh +++ b/dw/table.hh @@ -43,29 +43,26 @@ namespace dw { * sizeRequestImpl [color="#0000ff", URL="\ref dw::Table::sizeRequestImpl"]; * sizeAllocateImpl [color="#0000ff", * URL="\ref dw::Table::sizeAllocateImpl"]; + * getExtremes [color="#0000ff", URL="\ref dw::core::Widget::getExtremes"]; * getExtremesImpl [color="#0000ff", URL="\ref dw::Table::getExtremesImpl"]; * - * subgraph cluster_sizes { - * style="dashed"; color="#8080c0"; - * calcCellSizes [URL="\ref dw::Table::calcCellSizes"]; - * forceCalcCellSizes [URL="\ref dw::Table::forceCalcCellSizes"]; - * } - * - * subgraph cluster_extremes { - * style="dashed"; color="#8080c0"; - * calcColumnExtremes [URL="\ref dw::Table::calcColumnExtremes"]; - * forceCalcColumnExtremes[URL="\ref dw::Table::forceCalcColumnExtremes"]; - * } + * calcCellSizes [label="calcCellSizes (calcHeights = true)", + * URL="\ref dw::Table::calcCellSizes"]; + * forceCalcCellSizes [label="forceCalcCellSizes (calcHeights = true)", + * URL="\ref dw::Table::forceCalcCellSizes"]; + * actuallyCalcCellSizes[label="actuallyCalcCellSizes (calcHeights = true)", + * URL="\ref dw::Table::actuallyCalcCellSizes"]; + * forceCalcColumnExtremes[URL="\ref dw::Table::forceCalcColumnExtremes"]; * * sizeRequestImpl -> forceCalcCellSizes [label="[B]"]; * sizeAllocateImpl -> calcCellSizes [label="[A]"]; * getExtremesImpl -> forceCalcColumnExtremes [label="[B]"]; * - * forceCalcCellSizes -> calcColumnExtremes; + * forceCalcCellSizes -> actuallyCalcCellSizes; + * actuallyCalcCellSizes-> getExtremes; + * getExtremes -> getExtremesImpl [style="dashed", label="[C]"]; * * calcCellSizes -> forceCalcCellSizes [style="dashed", label="[C]"]; - * calcColumnExtremes -> forceCalcColumnExtremes [style="dashed", - * label="[C]"]; * } * \enddot * @@ -78,6 +75,12 @@ namespace dw { * [C] Whether this function is called, depends on NEEDS_RESIZE / * RESIZE_QUEUED / EXTREMES_CHANGED / EXTREMES_QUEUED. * + * **TODO:** + * + * - Are *[cC]alcCellSizes (calcHeights = *false*) not + * necessary anymore? + * - Calculating available sizes (Table::getAvailWidthOfChild) should + * be documented in this diagram, too. * *

Apportionment

* -- cgit v1.2.3 From d15ff594d989c0fdd025b9f66da8d83ff4dd2629 Mon Sep 17 00:00:00 2001 From: corvid Date: Wed, 3 Jun 2015 01:13:09 +0000 Subject: show certificate hash algorithm (and complain feebly if it's weak) --- src/IO/tls.c | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/IO/tls.c b/src/IO/tls.c index 4ae40961..f0f33215 100644 --- a/src/IO/tls.c +++ b/src/IO/tls.c @@ -943,15 +943,49 @@ static void Tls_print_cert_chain(SSL *ssl) if (sk) { const uint_t buflen = 4096; char buf[buflen]; - int i, n = sk_X509_num(sk); + int rc, i, n = sk_X509_num(sk); X509 *cert = NULL; EVP_PKEY *public_key; int key_type, key_bits; const char *type_str; + BIO *b; for (i = 0; i < n; i++) { cert = sk_X509_value(sk, i); public_key = X509_get_pubkey(cert); + + /* We are trying to find a way to get the hash function used + * with a certificate. This way, which is not very pleasant, puts + * a string such as "sha256WithRSAEncryption" in our buffer and we + * then trim off the "With..." part. + */ + b = BIO_new(BIO_s_mem()); + rc = i2a_ASN1_OBJECT(b, cert->sig_alg->algorithm); + + if (rc > 0) { + rc = BIO_gets(b, buf, buflen); + } + if (rc <= 0) { + strcpy(buf, "(unknown)"); + buf[buflen-1] = '\0'; + } else { + char *s = strstr(buf, "With"); + + if (s) { + *s = '\0'; + if (!strcmp(buf, "sha1")) { + MSG_WARN("In 2015, browsers have begun to deprecate SHA1 " + "certificates.\n"); + } else if (!strncmp(buf, "md", 2)) { + MSG_ERR("Browsers stopped accepting MD5 certificates around " + "2012.\n"); + } + } + } + BIO_free(b); + MSG("%s ", buf); + + key_type = EVP_PKEY_type(public_key->type); type_str = key_type == EVP_PKEY_RSA ? "RSA" : key_type == EVP_PKEY_DSA ? "DSA" : -- cgit v1.2.3 From 59b76c75b64578edac35d19c914067a0bd7791e9 Mon Sep 17 00:00:00 2001 From: corvid Date: Wed, 3 Jun 2015 07:54:16 +0000 Subject: web must be valid in order to continue in a_Http_connect_done A site triggers this with a background image where the style is deleted upon , and Capi_stop_client() finds that a_Cache_client_get_if_unique() is false, so nothing aborts the connection. And there's time for this to happen because we're doing TLS handshake. I don't know whether all of what triggered this is doing the right thing, but at least when it comes to capi, we can see that there's the idea of permitting it (with whether we ever actually want that in practice being yet another question). In any case, Http_make_query_str() definitely thinks the web is there. If we really decided that we wanted connections to continue without webs, we could stuff 1) what sort of thing are we requesting? 2) is this a third-party request? into the socket data. Making the query earlier is probably not advisable because we'd want the cookies available at the time that we send the query and not the cookies that were available somewhat earlier. --- src/IO/http.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/IO/http.c b/src/IO/http.c index 5aca1703..379d51c1 100644 --- a/src/IO/http.c +++ b/src/IO/http.c @@ -215,12 +215,14 @@ void a_Http_connect_done(int fd, bool_t success) if (fme && (sd = a_Klist_get_data(ValidSocks, fme->skey))) { ChainLink *info = sd->Info; + bool_t valid_web = a_Web_valid(sd->web); - if (success) { + if (success && valid_web) { a_Chain_bfcb(OpSend, info, &sd->SockFD, "FD"); Http_send_query(sd); } else { - MSG_BW(sd->web, 1, "Could not establish connection."); + if (valid_web) + MSG_BW(sd->web, 1, "Could not establish connection."); MSG("fd %d is done and failed\n", sd->SockFD); dClose(fd); Http_socket_free(VOIDP2INT(info->LocalKey)); /* free sd */ -- cgit v1.2.3