Adding webui_navigate

- Adding webui_navigate()
- Now when using all events webui_bind(MyWindow, "", events), WebUI will block all href requests
- Fix https://github.com/webui-dev/webui/issues/228
This commit is contained in:
Hassan DRAGA 2023-09-26 18:59:25 -04:00
parent 2ca3b793c4
commit 6a634cc680
5 changed files with 1461 additions and 1285 deletions

File diff suppressed because it is too large Load Diff

View File

@ -46,13 +46,14 @@ class WebuiBridge {
#hasEvents = false
#fnId = 1
#fnPromiseResolve: (((data: string) => unknown) | undefined)[] = []
#allowNavigation = false
// WebUI const
#HEADER_SIGNATURE = 221
#HEADER_JS = 254
#HEADER_JS_QUICK = 253
#HEADER_CLICK = 252
#HEADER_SWITCH = 251
#HEADER_NAVIGATION = 251
#HEADER_CLOSE = 250
#HEADER_CALL_FUNC = 249
#HEADER_SEND_RAW = 248
@ -116,8 +117,17 @@ class WebuiBridge {
// Handle navigation server side
if ('navigation' in globalThis) {
globalThis.navigation.addEventListener('navigate', (event) => {
const url = new URL(event.destination.url)
this.#sendEventNavigation(url.href)
if(!this.#allowNavigation) {
event.preventDefault()
const url = new URL(event.destination.url)
if (this.#hasEvents) {
if (this.#log) console.log(`WebUI -> DOM -> Navigation Event [${url.href}]`)
this.#sendEventNavigation(url.href)
}
else {
this.#close(this.#HEADER_NAVIGATION, url.href)
}
}
})
} else {
// Handle all link click to prevent natural navigation
@ -127,12 +137,21 @@ class WebuiBridge {
'a',
'click',
(event) => {
event.preventDefault()
const { href } = event.target as HTMLAnchorElement
if (this.#isExternalLink(href)) {
this.#close(this.#HEADER_SWITCH, href)
} else {
this.#sendEventNavigation(href)
if(!this.#allowNavigation) {
event.preventDefault()
const { href } = event.target as HTMLAnchorElement
if (this.#hasEvents) {
if (this.#log) console.log(`WebUI -> DOM -> Navigation Click Event [${href}]`)
// if (this.#isExternalLink(href)) {
// this.#close(this.#HEADER_NAVIGATION, href)
// } else {
// this.#sendEventNavigation(href)
// }
this.#sendEventNavigation(href)
}
else {
this.#close(this.#HEADER_NAVIGATION, href)
}
}
}
)
@ -161,7 +180,7 @@ class WebuiBridge {
}
#close(reason = 0, value = '') {
if (reason === this.#HEADER_SWITCH) this.#sendEventNavigation(value)
// if (reason === this.#HEADER_NAVIGATION) this.#sendEventNavigation(value)
this.#wsStatus = false
this.#closeReason = reason
this.#closeValue = value
@ -217,14 +236,13 @@ class WebuiBridge {
this.#ws.onclose = (event) => {
this.#wsStatus = false
if (this.#closeReason === this.#HEADER_SWITCH) {
if (this.#closeReason === this.#HEADER_NAVIGATION) {
if (this.#log) {
console.log(
`WebUI -> Connection lost -> Navigation to [${
this.#closeValue
}]`
`WebUI -> Connection closed -> Navigation to [${this.#closeValue}]`
)
}
this.#allowNavigation = true
globalThis.location.replace(this.#closeValue)
} else {
if (this.#log) {
@ -258,12 +276,12 @@ class WebuiBridge {
const callId = buffer8[2]
if (this.#log) {
console.log(`WebUI -> Call Response [${callResponse}]`)
console.log(`WebUI -> CMD -> Call Response [${callResponse}]`)
}
if (this.#fnPromiseResolve[callId]) {
if (this.#log) {
console.log(
`WebUI -> Resolving Response #${callId}...`
`WebUI -> CMD -> Resolving Response #${callId}...`
)
}
this.#fnPromiseResolve[callId]?.(callResponse)
@ -271,15 +289,15 @@ class WebuiBridge {
}
}
break
case this.#HEADER_SWITCH:
case this.#HEADER_NAVIGATION:
// Command Packet
// 0: [Signature]
// 1: [CMD]
// 2: [URL]
const url = this.#getDataStrFromPacket(buffer8, 2)
console.log(`WebUI -> Switch [${url}]`)
this.#close(this.#HEADER_SWITCH, url)
console.log(`WebUI -> CMD -> Navigation [${url}]`)
this.#close(this.#HEADER_NAVIGATION, url)
break
case this.#HEADER_NEW_ID:
// Command Packet
@ -288,7 +306,7 @@ class WebuiBridge {
// 2: [New Element]
const newElement = this.#getDataStrFromPacket(buffer8, 2)
console.log(`WebUI -> New Bind ID [${newElement}]`)
console.log(`WebUI -> CMD -> New Bind ID [${newElement}]`)
if(!this.#bindList.includes(newElement))
this.#bindList.push(newElement)
break
@ -308,7 +326,7 @@ class WebuiBridge {
)
if (this.#log)
console.log(`WebUI -> JS [${scriptSanitize}]`)
console.log(`WebUI -> CMD -> JS [${scriptSanitize}]`)
// Get callback result
let FunReturn = 'undefined'
@ -326,9 +344,9 @@ class WebuiBridge {
// Logging
if (this.#log && !FunError)
console.log(`WebUI -> JS -> Return [${FunReturn}]`)
console.log(`WebUI -> CMD -> JS -> Return [${FunReturn}]`)
if (this.#log && FunError)
console.log(`WebUI -> JS -> Error [${FunReturn}]`)
console.log(`WebUI -> CMD -> JS -> Error [${FunReturn}]`)
// Response Packet
// 0: [Signature]
@ -355,7 +373,7 @@ class WebuiBridge {
if (!this.#log)
globalThis.close()
else {
console.log(`WebUI -> Close`)
console.log(`WebUI -> CMD -> Close`)
this.#ws.close()
}
break
@ -381,6 +399,9 @@ class WebuiBridge {
// Get the raw data
const rawDataIndex: number = 2 + functionName.length + 1
const userRawData = buffer8.subarray(rawDataIndex);
if (this.#log)
console.log(`WebUI -> CMD -> Send Raw ${buffer8.length} bytes to [${functionName}()]`)
// Call the user function, and pass the raw data
if (typeof window[functionName] === 'function')
@ -432,23 +453,32 @@ class WebuiBridge {
0
)
this.#ws.send(packet.buffer)
if (this.#log) console.log(`WebUI -> Click [${elem}]`)
if (this.#log) console.log(`WebUI -> Send Click [${elem}]`)
}
}
#sendEventNavigation(url: string) {
if (this.#hasEvents && this.#wsStatus && url !== '') {
const packet = Uint8Array.of(
// Response Packet
// 0: [Signature]
// 1: [CMD]
// 2: [URL]
this.#HEADER_SIGNATURE,
this.#HEADER_SWITCH,
...new TextEncoder().encode(url)
)
this.#ws.send(packet.buffer)
if (this.#log) console.log(`WebUI -> Navigation [${url}]`)
if(url !== '') {
if(this.#hasEvents) {
if (this.#log) console.log(`WebUI -> Send Navigation Event [${url}]`)
if (this.#wsStatus && this.#hasEvents) {
const packet = Uint8Array.of(
// Packet
// 0: [Signature]
// 1: [CMD]
// 2: [URL]
this.#HEADER_SIGNATURE,
this.#HEADER_NAVIGATION,
...new TextEncoder().encode(url)
)
this.#ws.send(packet.buffer)
}
}
else {
if (this.#log) console.log(`WebUI -> Navigation To [${url}]`)
this.#allowNavigation = true
globalThis.location.replace(url)
}
}
}

View File

@ -25,8 +25,14 @@ void events(webui_event_t* e) {
printf("Disconnected. \n");
else if(e->event_type == WEBUI_EVENT_MOUSE_CLICK)
printf("Click. \n");
else if(e->event_type == WEBUI_EVENT_NAVIGATION)
else if(e->event_type == WEBUI_EVENT_NAVIGATION) {
printf("Starting navigation to: %s \n", (char *)e->data);
// Because we used `webui_bind(MyWindow, "", events);`
// then all `href` link clicks will be blocked by WebUI, so
// we need to use `webui_navigate()` to control the navigation
webui_navigate(e->window, (char *)e->data);
}
}
void switch_to_second_page(webui_event_t* e) {

View File

@ -427,6 +427,16 @@ WEBUI_EXPORT void webui_set_profile(size_t window, const char* name, const char*
*/
WEBUI_EXPORT const char* webui_get_url(size_t window);
/**
* @brief Navigate to a specific URL
*
* @param window The window number
* @param url Full HTTP URL
*
* @example webui_navigate(myWindow, "http://domain.com");
*/
WEBUI_EXPORT void webui_navigate(size_t window, const char* url);
// -- JavaScript ----------------------
// Run JavaScript without waiting for the response.

View File

@ -24,7 +24,7 @@
#define WEBUI_HEADER_JS 0xFE // JavaScript result in frontend
#define WEBUI_HEADER_JS_QUICK 0xFD // JavaScript result in frontend
#define WEBUI_HEADER_CLICK 0xFC // Click event
#define WEBUI_HEADER_SWITCH 0xFB // Frontend refresh
#define WEBUI_HEADER_NAVIGATION 0xFB // Frontend navigation
#define WEBUI_HEADER_CLOSE 0xFA // Close window
#define WEBUI_HEADER_CALL_FUNC 0xF9 // Backend function call
#define WEBUI_HEADER_SEND_RAW 0xF8 // Send raw binary data to the UI
@ -701,6 +701,39 @@ void webui_set_icon(size_t window, const char* icon, const char* icon_type) {
win->icon_type = icon_type_cpy;
}
void webui_navigate(size_t window, const char* url) {
#ifdef WEBUI_LOG
printf("[User] webui_navigate([%zu], [%s])...\n", window, url);
#endif
// Initialization
_webui_init();
// Dereference
if(_webui_core.exit_now || _webui_core.wins[window] == NULL) return;
_webui_window_t* win = _webui_core.wins[window];
if(!win->connected)
return;
// 0: [Signature]
// 1: [CMD]
// 2: [URL]
// Prepare packets
size_t packet_len = 2 + _webui_strlen(url); // [header][url]
char* packet = (char*) _webui_malloc(packet_len);
packet[0] = WEBUI_HEADER_SIGNATURE; // Signature
packet[1] = WEBUI_HEADER_NAVIGATION; // CMD
for(size_t i = 0; i < _webui_strlen(url); i++) // URL
packet[i + 2] = url[i];
// Send the packet
_webui_window_send(win, packet, packet_len);
_webui_free_mem((void*)packet);
}
bool webui_show(size_t window, const char* content) {
#ifdef WEBUI_LOG
@ -4282,9 +4315,9 @@ static bool _webui_show(_webui_window_t* win, const char* content, size_t browse
if(
strstr(content_cpy, "<html") ||
strstr(content_cpy, "<!DOCTYPE html>") ||
strstr(content_cpy, "<!doctype html>") ||
strstr(content_cpy, "<!Doctype html>")
strstr(content_cpy, "<!DOCTYPE") ||
strstr(content_cpy, "<!doctype") ||
strstr(content_cpy, "<!Doctype")
) {
// Embedded HTML
@ -4398,7 +4431,7 @@ static bool _webui_show_window(_webui_window_t* win, const char* content, bool i
size_t packet_len = 2 + _webui_strlen(url); // [header][url]
char* packet = (char*) _webui_malloc(packet_len);
packet[0] = WEBUI_HEADER_SIGNATURE; // Signature
packet[1] = WEBUI_HEADER_SWITCH; // CMD
packet[1] = WEBUI_HEADER_NAVIGATION; // CMD
for(size_t i = 0; i < _webui_strlen(win->url); i++) // URL
packet[i + 2] = win->url[i];
@ -4646,7 +4679,7 @@ static void _webui_window_receive(_webui_window_t* win, const char* packet, size
// Send ready signal to webui_script()
_webui_core.run_done[run_id] = true;
}
else if((unsigned char) packet[1] == WEBUI_HEADER_SWITCH) {
else if((unsigned char) packet[1] == WEBUI_HEADER_NAVIGATION) {
// Navigation Event
@ -4670,7 +4703,7 @@ static void _webui_window_receive(_webui_window_t* win, const char* packet, size
}
#ifdef WEBUI_LOG
printf("[Core]\t\t_webui_window_receive() -> WEBUI_HEADER_SWITCH \n");
printf("[Core]\t\t_webui_window_receive() -> WEBUI_HEADER_NAVIGATION \n");
printf("[Core]\t\t_webui_window_receive() -> URL size: %zu bytes \n", url_len);
printf("[Core]\t\t_webui_window_receive() -> URL: [%s] \n", url);
#endif