add sync tabs + refresh button
This commit is contained in:
202
tests/shared-profile-test.c
Normal file
202
tests/shared-profile-test.c
Normal file
@@ -0,0 +1,202 @@
|
||||
#include "protocol.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <spawn.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
extern char **environ;
|
||||
|
||||
struct view {
|
||||
pid_t pid;
|
||||
int control;
|
||||
int frames;
|
||||
int shared_fd;
|
||||
struct owb_shared_frames *shared;
|
||||
size_t shared_size;
|
||||
};
|
||||
|
||||
static bool write_all(int fd, const void *data, size_t size)
|
||||
{
|
||||
const uint8_t *cursor = data;
|
||||
while (size) {
|
||||
ssize_t count = write(fd, cursor, size);
|
||||
if (count < 0 && errno == EINTR)
|
||||
continue;
|
||||
if (count <= 0)
|
||||
return false;
|
||||
cursor += (size_t)count;
|
||||
size -= (size_t)count;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void serve_pages(int server)
|
||||
{
|
||||
static const char page[] =
|
||||
"<!doctype html><style>html,body{margin:0;width:100%;height:100%;background:rgb(2,3,5)}</style>"
|
||||
"<script>const p=new URL(location.href),t=p.searchParams.get('token'),"
|
||||
"k='owb-shared-profile-test',b=new BroadcastChannel(k);"
|
||||
"if(p.pathname==='/writer'){setInterval(()=>{localStorage.setItem(k,t);b.postMessage(t)},100)}"
|
||||
"else{let heard=false;b.onmessage=e=>{if(e.data===t)heard=true};"
|
||||
"setInterval(()=>{if(heard&&localStorage.getItem(k)===t)"
|
||||
"document.body.style.background='rgb(17,231,43)'},50)}</script>";
|
||||
char response[2048];
|
||||
int length = snprintf(response, sizeof(response),
|
||||
"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nCache-Control: no-store\r\n"
|
||||
"Connection: close\r\nContent-Length: %zu\r\n\r\n%s",
|
||||
strlen(page), page);
|
||||
for (;;) {
|
||||
int client = accept(server, NULL, NULL);
|
||||
if (client < 0 && errno == EINTR)
|
||||
continue;
|
||||
if (client < 0)
|
||||
_exit(0);
|
||||
char request[1024];
|
||||
ssize_t ignored = read(client, request, sizeof(request));
|
||||
(void)ignored;
|
||||
write_all(client, response, (size_t)length);
|
||||
close(client);
|
||||
}
|
||||
}
|
||||
|
||||
static bool start_view(struct view *view, const char *renderer, const char *url)
|
||||
{
|
||||
memset(view, 0, sizeof(*view));
|
||||
view->pid = 0;
|
||||
view->control = view->frames = view->shared_fd = -1;
|
||||
int control[2] = {-1, -1};
|
||||
int frames[2] = {-1, -1};
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM, 0, control) != 0 || pipe(frames) != 0)
|
||||
return false;
|
||||
const uint32_t width = 64, height = 64;
|
||||
view->shared_size = owb_shared_size(width, height);
|
||||
view->shared_fd = memfd_create("shared-profile-test", MFD_CLOEXEC);
|
||||
if (view->shared_fd < 0 || ftruncate(view->shared_fd, (off_t)view->shared_size) != 0)
|
||||
return false;
|
||||
view->shared = mmap(NULL, view->shared_size, PROT_READ | PROT_WRITE, MAP_SHARED,
|
||||
view->shared_fd, 0);
|
||||
if (view->shared == MAP_FAILED)
|
||||
return false;
|
||||
memset(view->shared, 0, sizeof(*view->shared));
|
||||
view->shared->magic = OWB_SHARED_MAGIC;
|
||||
view->shared->version = OWB_PROTOCOL_VERSION;
|
||||
view->shared->width = width;
|
||||
view->shared->height = height;
|
||||
view->shared->stride = width * 4;
|
||||
view->shared->slot_count = OWB_SHARED_FRAME_SLOTS;
|
||||
|
||||
posix_spawn_file_actions_t actions;
|
||||
posix_spawn_file_actions_init(&actions);
|
||||
posix_spawn_file_actions_adddup2(&actions, control[1], STDIN_FILENO);
|
||||
posix_spawn_file_actions_adddup2(&actions, frames[1], STDOUT_FILENO);
|
||||
posix_spawn_file_actions_adddup2(&actions, view->shared_fd, OWB_SHARED_FRAME_FD);
|
||||
char *arguments[] = {(char *)renderer, NULL};
|
||||
int error = posix_spawn(&view->pid, renderer, &actions, NULL, arguments, environ);
|
||||
posix_spawn_file_actions_destroy(&actions);
|
||||
close(control[1]);
|
||||
close(frames[1]);
|
||||
if (error)
|
||||
return false;
|
||||
view->control = control[0];
|
||||
view->frames = frames[0];
|
||||
struct owb_config_header config = {
|
||||
.magic = OWB_CONFIG_MAGIC, .version = OWB_PROTOCOL_VERSION,
|
||||
.width = width, .height = height, .fps = 20, .transparent = 0,
|
||||
.hardware_acceleration = 1, .url_length = (uint32_t)strlen(url), .css_length = 0,
|
||||
};
|
||||
return write_all(view->control, &config, sizeof(config)) &&
|
||||
write_all(view->control, url, strlen(url));
|
||||
}
|
||||
|
||||
static int stop_view(struct view *view)
|
||||
{
|
||||
int status = 0;
|
||||
if (view->control >= 0) {
|
||||
shutdown(view->control, SHUT_RDWR);
|
||||
close(view->control);
|
||||
}
|
||||
if (view->frames >= 0)
|
||||
close(view->frames);
|
||||
if (view->shared && view->shared != MAP_FAILED)
|
||||
munmap(view->shared, view->shared_size);
|
||||
if (view->shared_fd >= 0)
|
||||
close(view->shared_fd);
|
||||
if (view->pid > 0) {
|
||||
kill(view->pid, SIGTERM);
|
||||
while (waitpid(view->pid, &status, 0) < 0 && errno == EINTR) {
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc != 2)
|
||||
return 2;
|
||||
int server = socket(AF_INET, SOCK_STREAM, 0);
|
||||
int enabled = 1;
|
||||
setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled));
|
||||
struct sockaddr_in address = {.sin_family = AF_INET, .sin_addr.s_addr = htonl(INADDR_LOOPBACK)};
|
||||
if (server < 0 || bind(server, (struct sockaddr *)&address, sizeof(address)) != 0 ||
|
||||
listen(server, 16) != 0)
|
||||
return 77;
|
||||
socklen_t address_size = sizeof(address);
|
||||
if (getsockname(server, (struct sockaddr *)&address, &address_size) != 0)
|
||||
return 2;
|
||||
pid_t http_server = fork();
|
||||
if (http_server == 0)
|
||||
serve_pages(server);
|
||||
close(server);
|
||||
|
||||
char writer_url[256], reader_url[256];
|
||||
unsigned port = ntohs(address.sin_port);
|
||||
snprintf(writer_url, sizeof(writer_url), "http://127.0.0.1:%u/writer?token=%ld", port,
|
||||
(long)getpid());
|
||||
snprintf(reader_url, sizeof(reader_url), "http://127.0.0.1:%u/reader?token=%ld", port,
|
||||
(long)getpid());
|
||||
struct view writer, reader;
|
||||
bool started_writer = start_view(&writer, argv[1], writer_url);
|
||||
bool started_reader = started_writer && start_view(&reader, argv[1], reader_url);
|
||||
bool shared = false;
|
||||
for (unsigned attempt = 0; started_reader && attempt < 200; attempt++) {
|
||||
struct pollfd item = {.fd = reader.frames, .events = POLLIN};
|
||||
if (poll(&item, 1, 100) <= 0)
|
||||
continue;
|
||||
uint8_t slot;
|
||||
if (read(reader.frames, &slot, sizeof(slot)) != 1 || slot >= OWB_SHARED_FRAME_SLOTS)
|
||||
continue;
|
||||
uint8_t *frame = owb_shared_slot(reader.shared, slot);
|
||||
size_t center = ((size_t)32 * 64 + 32) * 4;
|
||||
uint8_t *bgra = frame + center;
|
||||
if (bgra[0] < 55 && bgra[1] > 210 && bgra[2] < 35 && bgra[3] == 255) {
|
||||
shared = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int reader_status = started_reader ? stop_view(&reader) : 0;
|
||||
int writer_status = started_writer ? stop_view(&writer) : 0;
|
||||
kill(http_server, SIGTERM);
|
||||
waitpid(http_server, NULL, 0);
|
||||
if (!started_writer || !started_reader)
|
||||
return 2;
|
||||
if ((WIFEXITED(reader_status) && WEXITSTATUS(reader_status) == 5) ||
|
||||
(WIFEXITED(writer_status) && WEXITSTATUS(writer_status) == 5))
|
||||
return 77;
|
||||
if (!shared) {
|
||||
fprintf(stderr, "two renderer views did not share localStorage and BroadcastChannel\n");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user