Files
LeencyandBurer 145ac1ad7b Webview 4.0 (#14)
Рендеринг:
- Новый движок вместо 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>
2026-09-27 09:45:21 +03:00

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;
}