Рендеринг: - Новый движок вместо TWB: страница проходит через DOM, вычисление стилей, раскладку и отрисовку отдельными этапами - Рисуется только видимая область, холст размером с окно - расход памяти не растёт с длиной страницы - Прокрутка сдвигает уже нарисованный холст и дорисовывает только появившуюся полосу - Страница показывается, не дожидаясь картинок; таблицы стилей, наоборот, дожидаются - до них на экране остаётся предыдущая страница CSS: - Источники правил: <style>, атрибут style="", <link> и @import - Селекторы: тег, .class, #id, *, атрибутные, комбинаторы - пробел, >, +, ~ - :is() и :where() раскрываются в обычные правила - Псевдоклассы: first-child, last-child, only-child, first-of-type, last-of-type, only-of-type, root, empty, checked, disabled, not, nth-child, nth-last-child, nth-of-type, nth-last-of-type - Псевдоэлементы ::before и ::after - @media по ширине и высоте окна - var(), calc(), min(), max(), clamp() - Блочная модель целиком: margin, padding, border, min/max-width и height - float, flex, grid - position: static, relative, absolute, fixed, sticky, z-index - vertical-align, в том числе super и sub; теги <sup> и <sub> Таблицы: - colspan и rowspan, неявные строки и ячейки, <caption>, вложенные таблицы - Ширины колонок считаются по содержимому и по заданным процентам - <table align> и <center> вокруг таблицы Сеть и кэш: - Картинки и таблицы стилей грузятся параллельно потоками самого WebView - до 12 одновременно, вместо отдельного процесса dl на каждый файл - Снято ограничение в 256 байт на адрес, и запросы уходят с куками - Кэш файлов в /tmp0/1/Cache с индексом: повторный заход на страницу не перекачивает картинки - SVG и WebP открываются через конвертер (по умолчанию wsrv.nl, адрес меняется в app.ini) - Куки: приём, хранение и отправка - Оборванное соединение распознаётся - обрезанный файл не сохраняется и не попадает в кэш, страница помечается неполной Формы: - Отправка по GET и по POST - Контролы: текстовое поле, пароль, чекбокс, радиокнопка, обычная кнопка и кнопка отправки, <select>, <textarea> - Размеры контролов берутся из CSS и атрибутов страницы, а не фиксированы Текст и шрифты: - Выделение и копирование текста, поиск по странице - Курсив для <i>, <em>, <cite>, <var>, <dfn>, <address> и font-style: системные шрифты курсива не имеют, поэтому нарисованная строка наклоняется сдвигом пикселей - Мелкий шрифт для текста 10 px и меньше - но только если такого текста меньше половины страницы - Изменение размера шрифта: Ctrl+- и Ctrl++ - Определение кодировки по <meta charset> и по содержимому Интерфейс: - Обновлены встроенные страницы: новая вкладка и страница ошибки - Пункт меню "Открыть в NetSurf" - Кнопка папки загрузок на панели - Правый клик по картинке: открыть, сохранить, скопировать адрес - До 5 вкладок Убрано: - Страница истории WebView:history и пункт меню к ней Изменения вне браузера: - dl: оборванная загрузка считается ошибкой, обрезанный файл не сохраняется; путь назначения принимается после | в параметре - menu: не реагирует на события мыши до создания окна - иначе меню закрывалось сразу после запуска - lib/obj/http.h: в _http появилось поле flags, наружу выведен HTTP_GOT_ALL_DATA - lib/draw_buf.h: холст держит размер, заданный Init(); неиспользуемые методы не попадают в программу - lib/collection.h: swap() для двух записей - lib/obj/box_lib.h: edit_box приведена к раскладке переписанного box_lib (OS PR #662) Размер запакованного приложения - 64 КБ: 65 261 байт (EN) и 65 456 (RU). --------- Co-authored-by: Burer <burer@kolibrios.org> Reviewed-on: #14 Reviewed-by: Burer <burer@kolibrios.org> Co-authored-by: leency <lipatov.kiril@gmail.com>
269 lines
8.0 KiB
C
269 lines
8.0 KiB
C
// Pictures and stylesheets of a page are fetched by threads of WebView itself, one per
|
|
// file. They used to be "dl -i" processes: a program loaded from disk for every picture,
|
|
// a 256 byte limit on the command line (longer urls were skipped) and no cookies.
|
|
// libHTTP's get() waits for the name lookup and the connection, so a thread does it and
|
|
// the window stays live. The thread writes the file to the path it was given and exits;
|
|
// the window sees it gone (GetProcessSlot works for a thread id too) and takes the file,
|
|
// the way it did with dl.
|
|
//
|
|
// A thread must not touch what the window thread writes: no sprintf into globals, no
|
|
// CreateFile (it fills the shared f70), no "http" struct, no cookie buffers. What it
|
|
// needs is copied into the memory of its job before it starts.
|
|
|
|
// not defined: every file is fetched by a "dl -i" process, as before (WebView.c ImgStart)
|
|
#define IMG_THREADS
|
|
|
|
#define IMG_JOBS 64 // >= IMG_PARALLEL + IMG_ORPHANS (60): every slot and every orphan has a job
|
|
#define IMGJ_STACK 32768 // the thread's stack, at the start of the job memory
|
|
#define IMGJ_URL 32768 // the address asked for (URL_SIZE + 1)
|
|
#define IMGJ_GET 36772 // what libHTTP gets: https in the proxy form (URL_SIZE + 256 + 1)
|
|
#define IMGJ_HDR 41032 // request headers: Accept-Language and the cookies of the host
|
|
#define IMGJ_PATH 45128 // the file to write (256)
|
|
#define IMGJ_SIZE 45384
|
|
|
|
dword img_job_mem[IMG_JOBS]; // allocated when the job is first used, then kept
|
|
dword img_job_tid[IMG_JOBS]; // its thread, 0 = never started
|
|
dword img_job_cancel[IMG_JOBS]; // set by the window: stop, write nothing
|
|
dword img_job_phase[IMG_JOBS]; // what the thread does now: JP_* (ImgJobsDump)
|
|
dword img_job_bytes[IMG_JOBS]; // received so far
|
|
dword img_job_flags[IMG_JOBS]; // http_msg.flags of the transfer
|
|
enum { JP_START = 1, JP_GET, JP_RECV, JP_SAVE, JP_END };
|
|
#ifdef TESTBUILD
|
|
bool img_job_dump = false; // tests: a job slower than 10 s writes /hd0/1/jobs.txt
|
|
bool img_job_dumped;
|
|
#endif
|
|
|
|
// the address libHTTP gets: https goes through the proxy. _get holds URL_SIZE + 257
|
|
// bytes (IMGJ_GET, WebView.c http_url_buf); the page load uses it too (GetUrl)
|
|
bool img_job_url(dword _url, _get)
|
|
{
|
|
IF (!strncmp(_url, "http://", 7)) { strlcpy(_get, _url, URL_SIZE); RETURN true; }
|
|
IF (strncmp(_url, "https://", 8)) RETURN false;
|
|
IF (!proxy_address[0]) RETURN false;
|
|
strcpy(_get, #proxy_address);
|
|
strcat_proxy_site(_get, _url, URL_SIZE + 257);
|
|
RETURN true;
|
|
}
|
|
|
|
// one GET received to the end: the finished transfer, 0 when it failed or was cancelled
|
|
dword img_job_get(int _j, dword _get, _hdr)
|
|
{
|
|
dword t, r;
|
|
img_job_phase[_j] = JP_GET;
|
|
http_get stdcall (_get, 0, 0, _hdr);
|
|
t = EAX;
|
|
IF (!t) RETURN 0;
|
|
img_job_phase[_j] = JP_RECV;
|
|
loop() {
|
|
IF (img_job_cancel[_j]) BREAK;
|
|
http_receive stdcall (t);
|
|
r = EAX;
|
|
EDI = t;
|
|
img_job_bytes[_j] = EDI.http_msg.content_received;
|
|
EDI = t;
|
|
img_job_flags[_j] = EDI.http_msg.flags;
|
|
IF (!r) RETURN t;
|
|
WaitEventTimeout(2); // EVM_STACK: wakes up when data comes
|
|
}
|
|
http_free stdcall (t);
|
|
RETURN 0;
|
|
}
|
|
|
|
// a header value up to the end of its line
|
|
void img_job_line(dword _dst, _src, _size)
|
|
{
|
|
dword e, c;
|
|
e = _dst + _size - 1;
|
|
WHILE (_dst < e) {
|
|
c = DSBYTE[_src];
|
|
IF (!c) || (c == 13) || (c == 10) BREAK;
|
|
DSBYTE[_dst] = c;
|
|
_dst++;
|
|
_src++;
|
|
}
|
|
DSBYTE[_dst] = 0;
|
|
}
|
|
|
|
// a 3xx answer: its Location made absolute against _url into _url (_buf is scratch)
|
|
bool img_job_redirect(dword _t, _url, _buf, _hops)
|
|
{
|
|
dword p;
|
|
IF (_hops > 5) RETURN false;
|
|
http_find_header_field stdcall (_t, "location");
|
|
p = EAX;
|
|
IF (!p) RETURN false;
|
|
img_job_line(_buf, p, URL_SIZE);
|
|
get_absolute_url(_buf, _url);
|
|
strlcpy(_url, _buf, URL_SIZE);
|
|
RETURN true;
|
|
}
|
|
|
|
// write the file with a request block of our own: CreateFile() fills the shared f70
|
|
void img_job_save(dword _path, _data, _size)
|
|
{
|
|
char blk[28];
|
|
dword b;
|
|
b = #blk;
|
|
DSDWORD[b] = 2;
|
|
DSDWORD[b+4] = 0;
|
|
DSDWORD[b+8] = 0;
|
|
DSDWORD[b+12] = _size;
|
|
DSDWORD[b+16] = _data;
|
|
DSBYTE[b+20] = 0;
|
|
DSDWORD[b+21] = _path;
|
|
EBX = b;
|
|
EAX = 70;
|
|
$int 0x40
|
|
}
|
|
|
|
// the work of the thread: the file of job _j, following up to 5 redirects. Only a
|
|
// complete 2xx answer is written: an error page must not be stored as the picture, and
|
|
// http_receive() also ends with 0 on a timeout or a dropped connection - a picture or
|
|
// a stylesheet cut there would stay in the cache as it is.
|
|
void img_job_run(int _j)
|
|
{
|
|
dword mem, ju, jg, jh, t, code, p, n, all;
|
|
int hops;
|
|
bool ok;
|
|
mem = img_job_mem[_j];
|
|
ju = mem + IMGJ_URL;
|
|
jg = mem + IMGJ_GET;
|
|
jh = mem + IMGJ_HDR;
|
|
hops = 0;
|
|
loop() {
|
|
IF (!img_job_url(ju, jg)) return;
|
|
t = img_job_get(_j, jg, jh);
|
|
IF (!t) return;
|
|
EDI = t;
|
|
code = EDI.http_msg.status;
|
|
IF (code < 300) || (code >= 400) BREAK;
|
|
hops++;
|
|
ok = img_job_redirect(t, ju, jg, hops);
|
|
http_free stdcall (t);
|
|
IF (!ok) RETURN;
|
|
jh = #accept_language; // the cookies were for the first host only
|
|
}
|
|
EDI = t;
|
|
p = EDI.http_msg.content_ptr;
|
|
n = EDI.http_msg.content_received;
|
|
EDI = t;
|
|
all = EDI.http_msg.flags & HTTP_GOT_ALL_DATA;
|
|
IF (code >= 200) && (code < 300) && (p) && (n) && (all) {
|
|
IF (!img_job_cancel[_j]) {
|
|
img_job_phase[_j] = JP_SAVE;
|
|
jh = mem + IMGJ_PATH;
|
|
img_job_save(jh, p, n);
|
|
}
|
|
}
|
|
http_free stdcall (t); // it frees content_ptr as well
|
|
}
|
|
|
|
// the thread of one job: the job whose memory holds this stack
|
|
void img_worker()
|
|
{
|
|
char mark[4];
|
|
dword here, m;
|
|
int j;
|
|
here = #mark;
|
|
FOR (j = 0; j < IMG_JOBS; j++) {
|
|
m = img_job_mem[j];
|
|
IF (!m) CONTINUE;
|
|
IF (here < m) CONTINUE;
|
|
m += IMGJ_STACK;
|
|
IF (here >= m) CONTINUE;
|
|
SetEventMask(EVM_STACK);
|
|
img_job_run(j);
|
|
img_job_phase[j] = JP_END;
|
|
BREAK;
|
|
}
|
|
ExitProcess(); // ends this thread only
|
|
}
|
|
|
|
//===================================================//
|
|
// the window thread side //
|
|
//===================================================//
|
|
|
|
dword ImgJobMem(int _j)
|
|
{
|
|
dword m;
|
|
m = img_job_mem[_j];
|
|
IF (m) RETURN m;
|
|
m = malloc(IMGJ_SIZE);
|
|
img_job_mem[_j] = m;
|
|
RETURN m;
|
|
}
|
|
|
|
// start a thread fetching _url into the file _path: its id, 0 when it could not start
|
|
int ImgJobStart(dword _url, _path)
|
|
{
|
|
int j, tid;
|
|
dword m, p, h;
|
|
FOR (j = 0; j < IMG_JOBS; j++) {
|
|
IF (!img_job_tid[j]) BREAK;
|
|
IF (!GetProcessSlot(img_job_tid[j])) BREAK;
|
|
}
|
|
IF (j >= IMG_JOBS) return 0;
|
|
m = ImgJobMem(j);
|
|
IF (!m) return 0;
|
|
p = m + IMGJ_URL;
|
|
strlcpy(p, _url, URL_SIZE);
|
|
h = cookies_request(_url);
|
|
p = m + IMGJ_HDR;
|
|
strlcpy(p, h, 4095);
|
|
p = m + IMGJ_PATH;
|
|
strlcpy(p, _path, 255);
|
|
img_job_cancel[j] = 0;
|
|
img_job_phase[j] = JP_START;
|
|
img_job_bytes[j] = 0;
|
|
img_job_flags[j] = 0;
|
|
p = m + IMGJ_STACK;
|
|
p -= 16;
|
|
tid = CreateThread(#img_worker, p);
|
|
IF (tid <= 0) RETURN 0;
|
|
img_job_tid[j] = tid;
|
|
RETURN tid;
|
|
}
|
|
|
|
// the window gives up on thread _tid: it stops at its next read and writes nothing
|
|
void ImgJobCancel(int _tid)
|
|
{
|
|
int j;
|
|
FOR (j = 0; j < IMG_JOBS; j++) {
|
|
IF (img_job_tid[j] == _tid) img_job_cancel[j] = 1;
|
|
}
|
|
}
|
|
|
|
#ifdef TESTBUILD
|
|
// the jobs whose thread still runs, into /hd0/1/jobs.txt: where a slow page waits
|
|
void ImgJobsDump()
|
|
{
|
|
dword out, p;
|
|
int j;
|
|
char line[512];
|
|
out = malloc(IMG_JOBS * 512 + 16);
|
|
IF (!out) RETURN;
|
|
DSBYTE[out] = 0;
|
|
FOR (j = 0; j < IMG_JOBS; j++) {
|
|
IF (!img_job_tid[j]) CONTINUE;
|
|
IF (!GetProcessSlot(img_job_tid[j])) CONTINUE;
|
|
p = img_job_mem[j];
|
|
p += IMGJ_URL;
|
|
sprintf(#line, "job %i tid %i phase %i bytes %i flags %a cancel %i ", j, img_job_tid[j], img_job_phase[j], img_job_bytes[j], img_job_flags[j], img_job_cancel[j]);
|
|
strcat(out, #line);
|
|
strlcpy(#line, p, 300);
|
|
strcat(out, #line);
|
|
strcat(out, "\n");
|
|
}
|
|
p = strlen(out);
|
|
CreateFile(p, out, "/hd0/1/jobs.txt");
|
|
free(out);
|
|
}
|
|
#endif
|
|
|
|
// leaving WebView: the threads outlive the window thread, they must not go on
|
|
void ImgJobsCancelAll()
|
|
{
|
|
int j;
|
|
FOR (j = 0; j < IMG_JOBS; j++) img_job_cancel[j] = 1;
|
|
}
|