Skip to content

Commit

Permalink
Rework RPC code and examples
Browse files Browse the repository at this point in the history
* Rename parameter "seq" (sequential number) to "id" (identifier).

* Generate IDs with a length of 32 random characters.

* Replace user (init) script on bind/unbind to fix bug where scripts are
  never removed and instead pile up for each bind/unbind operation.

* JS RPC operations are now exposed through "window.__webview__".

  A bound function can now be invoked via JS as follows:

      window.__webview__.call(name, ...args)

  The following is still possible and forwards to "__webview__.call()":

      window.count(...args)

* Modernize examples.
  • Loading branch information
SteffenL committed Feb 25, 2024
1 parent fb6b17d commit 4f72bd0
Show file tree
Hide file tree
Showing 4 changed files with 579 additions and 226 deletions.
99 changes: 51 additions & 48 deletions examples/bind.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,85 +75,88 @@ void thread_sleep(int seconds) {

typedef struct {
webview_t w;
unsigned int count;
long count;
} context_t;

static const char html[] =
"<button id=\"increment\">Tap me</button>\n"
"<div>You tapped <span id=\"count\">0</span> time(s).</div>\n"
"<button id=\"compute\">Compute</button>\n"
"<div>Result of computation: <span id=\"compute-result\">0</span></div>\n"
"<script>\n"
" const [incrementElement, countElement, computeElement, "
"computeResultElement] =\n"
" document.querySelectorAll(\"#increment, #count, #compute, "
"#compute-result\");\n"
" document.addEventListener(\"DOMContentLoaded\", () => {\n"
" incrementElement.addEventListener(\"click\", () => {\n"
" window.increment().then(result => {\n"
" countElement.textContent = result.count;\n"
" });\n"
" });\n"
" computeElement.addEventListener(\"click\", () => {\n"
" computeElement.disabled = true;\n"
" window.compute(6, 7).then(result => {\n"
" computeResultElement.textContent = result;\n"
" computeElement.disabled = false;\n"
" });\n"
" });\n"
" });\n"
"</script>";

void increment(const char *seq, const char *req, void *arg) {
UNUSED(req);
static const char html[] = "\
<div>\n\
<button id=\"increment\">+</button>\n\
<button id=\"decrement\">−</button>\n\
<span>Counter: <span id=\"counterResult\">0</span></span>\n\
</div>\n\
<hr />\n\
<div>\n\
<button id=\"compute\">Compute</button>\n\
<span>Result: <span id=\"computeResult\">(not started)</span></span>\n\
</div>\n\
<script type=\"module\">\n\
const getElements = ids => Object.assign({}, ...ids.map(\n\
id => ({ [id]: document.getElementById(id) })));\n\
const ui = getElements([\n\
\"increment\", \"decrement\", \"counterResult\", \"compute\",\n\
\"computeResult\"\n\
]);\n\
ui.increment.addEventListener(\"click\", async () => {\n\
ui.counterResult.textContent = await window.count(1);\n\
});\n\
ui.decrement.addEventListener(\"click\", async () => {\n\
ui.counterResult.textContent = await window.count(-1);\n\
});\n\
ui.compute.addEventListener(\"click\", async () => {\n\
ui.compute.disabled = true;\n\
ui.computeResult.textContent = \"(pending)\";\n\
ui.computeResult.textContent = await window.compute(6, 7);\n\
ui.compute.disabled = false;\n\
});\n\
</script>";

void count(const char *id, const char *req, void *arg) {
context_t *context = (context_t *)arg;
char count_string[10] = {0};
sprintf(count_string, "%u", ++context->count);
char result[21] = {0};
strcat(result, "{\"count\": ");
strcat(result, count_string);
strcat(result, "}");
webview_return(context->w, seq, 0, result);
// Imagine that params->req is properly parsed or use your own JSON parser.
long direction = strtol(req + 1, NULL, 10);
char result[10] = {0};
sprintf(result, "%ld", context->count += direction);
webview_return(context->w, id, 0, result);
}

typedef struct {
webview_t w;
char *seq;
char *id;
char *req;
} compute_thread_params_t;

compute_thread_params_t *
compute_thread_params_create(webview_t w, const char *seq, const char *req) {
compute_thread_params_create(webview_t w, const char *id, const char *req) {
compute_thread_params_t *params =
(compute_thread_params_t *)malloc(sizeof(compute_thread_params_t));
params->w = w;
params->seq = (char *)malloc(strlen(seq) + 1);
params->id = (char *)malloc(strlen(id) + 1);
params->req = (char *)malloc(strlen(req) + 1);
strcpy(params->seq, seq);
strcpy(params->id, id);
strcpy(params->req, req);
return params;
}

void compute_thread_params_free(compute_thread_params_t *p) {
free(p->req);
free(p->seq);
free(p->id);
free(p);
}

void compute_thread_proc(void *arg) {
compute_thread_params_t *params = (compute_thread_params_t *)arg;
// Simulate load.
thread_sleep(1);
// Either imagine that params->req is parsed here or use your own JSON parser.
// Imagine that params->req is properly parsed or use your own JSON parser.
const char *result = "42";
webview_return(params->w, params->seq, 0, result);
webview_return(params->w, params->id, 0, result);
compute_thread_params_free(params);
}

void compute(const char *seq, const char *req, void *arg) {
void compute(const char *id, const char *req, void *arg) {
context_t *context = (context_t *)arg;
compute_thread_params_t *params =
compute_thread_params_create(context->w, seq, req);
compute_thread_params_create(context->w, id, req);
// Create a thread and forget about it for the sake of simplicity.
if (thread_create(compute_thread_proc, params) != 0) {
compute_thread_params_free(params);
Expand All @@ -175,10 +178,10 @@ int main() {
webview_set_title(w, "Bind Example");
webview_set_size(w, 480, 320, WEBVIEW_HINT_NONE);

// A binding that increments a value and immediately returns the new value.
webview_bind(w, "increment", increment, &context);
// A binding that counts up or down and immediately returns the new value.
webview_bind(w, "count", count, &context);

// An binding that creates a new thread and returns the result at a later time.
// A binding that creates a new thread and returns the result at a later time.
webview_bind(w, "compute", compute, &context);

webview_set_html(w, html);
Expand Down
78 changes: 43 additions & 35 deletions examples/bind.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,35 @@
#include <thread>

constexpr const auto html =
R"html(<button id="increment">Tap me</button>
<div>You tapped <span id="count">0</span> time(s).</div>
<button id="compute">Compute</button>
<div>Result of computation: <span id="compute-result">0</span></div>
<script>
const [incrementElement, countElement, computeElement, computeResultElement] =
document.querySelectorAll("#increment, #count, #compute, #compute-result");
document.addEventListener("DOMContentLoaded", () => {
incrementElement.addEventListener("click", () => {
window.increment().then(result => {
countElement.textContent = result.count;
});
});
computeElement.addEventListener("click", () => {
computeElement.disabled = true;
window.compute(6, 7).then(result => {
computeResultElement.textContent = result;
computeElement.disabled = false;
});
});
R"html(
<div>
<button id="increment">+</button>
<button id="decrement">−</button>
<span>Counter: <span id="counterResult">0</span></span>
</div>
<hr />
<div>
<button id="compute">Compute</button>
<span>Result: <span id="computeResult">(not started)</span></span>
</div>
<script type="module">
const getElements = ids => Object.assign({}, ...ids.map(
id => ({ [id]: document.getElementById(id) })));
const ui = getElements([
"increment", "decrement", "counterResult", "compute",
"computeResult"
]);
ui.increment.addEventListener("click", async () => {
ui.counterResult.textContent = await window.count(1);
});
ui.decrement.addEventListener("click", async () => {
ui.counterResult.textContent = await window.count(-1);
});
ui.compute.addEventListener("click", async () => {
ui.compute.disabled = true;
ui.computeResult.textContent = "(pending)";
ui.computeResult.textContent = await window.compute(6, 7);
ui.compute.disabled = false;
});
</script>)html";

Expand All @@ -34,31 +43,30 @@ int WINAPI WinMain(HINSTANCE /*hInst*/, HINSTANCE /*hPrevInst*/,
#else
int main() {
#endif
unsigned int count = 0;
webview::webview w(false, nullptr);
long count = 0;

webview::webview w(true, nullptr);
w.set_title("Bind Example");
w.set_size(480, 320, WEBVIEW_HINT_NONE);

// A binding that increments a value and immediately returns the new value.
w.bind("increment", [&](const std::string & /*req*/) -> std::string {
auto count_string = std::to_string(++count);
return "{\"count\": " + count_string + "}";
// A binding that counts up or down and immediately returns the new value.
w.bind("count", [&](const std::string &req) -> std::string {
// Imagine that req is properly parsed or use your own JSON parser.
auto direction = std::stol(req.substr(1, req.size() - 1));
return std::to_string(count += direction);
});

// An binding that creates a new thread and returns the result at a later time.
// A binding that creates a new thread and returns the result at a later time.
w.bind(
"compute",
[&](const std::string &seq, const std::string &req, void * /*arg*/) {
[&](const std::string &id, const std::string &req, void * /*arg*/) {
// Create a thread and forget about it for the sake of simplicity.
std::thread([&, seq, req] {
std::thread([&, id, req] {
// Simulate load.
std::this_thread::sleep_for(std::chrono::seconds(1));
// json_parse() is an implementation detail and is only used here
// to provide a working example.
auto left = std::stoll(webview::detail::json_parse(req, "", 0));
auto right = std::stoll(webview::detail::json_parse(req, "", 1));
auto result = std::to_string(left * right);
w.resolve(seq, 0, result);
// Imagine that req is properly parsed or use your own JSON parser.
const auto *result = "42";
w.resolve(id, 0, result);
}).detach();
},
nullptr);
Expand Down
Loading

0 comments on commit 4f72bd0

Please sign in to comment.