mirror of
https://github.com/google/pebble.git
synced 2025-07-08 15:50:26 -04:00
351 lines
12 KiB
C
351 lines
12 KiB
C
/*
|
|
* Copyright 2024 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "rocky_api_global.h"
|
|
|
|
#include "applib/app_logging.h"
|
|
#include "jerry-api.h"
|
|
#include "kernel/pbl_malloc.h"
|
|
#include "system/passert.h"
|
|
#include "syscall/syscall.h"
|
|
#include "util/size.h"
|
|
|
|
#include "rocky_api.h"
|
|
#include "rocky_api_errors.h"
|
|
#include "rocky_api_util.h"
|
|
|
|
#define ROCKY_LISTENERS "_listeners"
|
|
|
|
#define ROCKY_ON "on"
|
|
#define ROCKY_ADD_EVENT_LISTENER "addEventListener"
|
|
#define ROCKY_OFF "off"
|
|
#define ROCKY_REMOVE_EVENT_LISTENER "removeEventListener"
|
|
#define ROCKY_EVENT_CONSTRUCTOR "Event"
|
|
#define ROCKY_EVENT_TYPE "type"
|
|
|
|
// TODO: PBL-35780 make this part of app_state_get_rocky_runtime_context()
|
|
SECTION(".rocky_bss") static const RockyGlobalAPI *const *s_global_apis;
|
|
|
|
#define API_REFS_FOREACH(var_name) \
|
|
for (RockyGlobalAPI const *const *var_name = s_global_apis; *var_name != NULL; var_name++)
|
|
|
|
|
|
static jerry_value_t prv_get_or_create_listener_array(const char *event_name) {
|
|
JS_VAR rocky = rocky_get_rocky_singleton();
|
|
JS_VAR all_listeners = rocky_get_or_create_object(rocky, ROCKY_LISTENERS,
|
|
rocky_creator_object,
|
|
NULL, NULL);
|
|
return rocky_get_or_create_object(all_listeners, event_name,
|
|
rocky_creator_empty_array,
|
|
NULL, NULL);
|
|
}
|
|
|
|
// callback while iterating over listeners
|
|
// @param event_listeners the array this listener is part of
|
|
// @param idx position within the array at which the listener exists
|
|
// @param listener the JS function that's registered as listener
|
|
// @param data pointer provided when calling prv_iterate_event_listeners()
|
|
// @return true, if you interested in further iterations. False to stop iterating.
|
|
typedef bool (*EventListenerIteratorCb)(jerry_value_t event_listeners, uint32_t idx,
|
|
jerry_value_t listener, void *data);
|
|
|
|
static void prv_iterate_event_listeners(const char *event_name,
|
|
EventListenerIteratorCb callback,
|
|
void *data) {
|
|
PBL_ASSERTN(callback);
|
|
|
|
JS_VAR rocky = rocky_get_rocky_singleton();
|
|
JS_VAR all_listeners = jerry_get_object_field(rocky, "_listeners");
|
|
JS_VAR event_listeners = jerry_get_object_field(all_listeners, event_name);
|
|
// printf("event_listeners.refcount: %d", jerry_port_)
|
|
|
|
const uint32_t len = jerry_get_array_length(event_listeners);
|
|
for (uint32_t idx = 0; idx < len; idx++) {
|
|
JS_VAR listener = jerry_get_property_by_index(event_listeners, idx);
|
|
if (jerry_value_is_function(listener)) {
|
|
bool wants_more = callback(event_listeners, idx, listener, data);
|
|
if (!wants_more) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
jerry_value_t listener;
|
|
bool found;
|
|
} ListenerQueryData;
|
|
|
|
static bool prv_find_listener(jerry_value_t event_listeners, uint32_t idx,
|
|
jerry_value_t listener, void *data) {
|
|
ListenerQueryData *query_data = data;
|
|
if (query_data->listener == listener) {
|
|
query_data->found = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool prv_listener_is_registered(const char *event_name, jerry_value_t listener) {
|
|
ListenerQueryData data = {
|
|
.listener = listener,
|
|
};
|
|
prv_iterate_event_listeners(event_name, prv_find_listener, &data);
|
|
return data.found;
|
|
}
|
|
|
|
T_STATIC void prv_add_event_listener_to_list(const char *event_name, jerry_value_t listener) {
|
|
if (prv_listener_is_registered(event_name, listener)) {
|
|
// we won't register the same listener twice
|
|
return;
|
|
}
|
|
JS_VAR listeners = prv_get_or_create_listener_array(event_name);
|
|
const uint32_t num_entries = jerry_get_array_length(listeners);
|
|
JS_UNUSED_VAL = jerry_set_property_by_index(listeners, num_entries, listener);
|
|
}
|
|
|
|
static bool prv_remove_listener(jerry_value_t event_listeners, uint32_t idx,
|
|
jerry_value_t listener, void *data) {
|
|
ListenerQueryData *query_data = data;
|
|
if (query_data->listener == listener) {
|
|
// calling `event_listeners.splice(idx, 1)` to remove item at idx
|
|
// wow, this is a lot of code for this
|
|
JS_VAR splice = jerry_get_object_field(event_listeners, "splice");
|
|
const jerry_value_t args[] = {jerry_create_number(idx), jerry_create_number(1)};
|
|
JS_VAR remove_result = jerry_call_function(splice, event_listeners,
|
|
args, ARRAY_LENGTH(args));
|
|
if (jerry_value_has_error_flag(remove_result)) {
|
|
rocky_log_exception("removing event listener", remove_result);
|
|
}
|
|
for (size_t i = 0; i < ARRAY_LENGTH(args); i++) {
|
|
jerry_release_value(args[i]);
|
|
}
|
|
|
|
// mark item as removed and stop iterating
|
|
query_data->found = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
T_STATIC bool prv_remove_event_listener_from_list(const char *event_name, jerry_value_t listener) {
|
|
ListenerQueryData data = {
|
|
.listener = listener,
|
|
};
|
|
prv_iterate_event_listeners(event_name, prv_remove_listener, &data);
|
|
return data.found;
|
|
}
|
|
|
|
|
|
// implementation of .on(event_name, handler) and stores them in a new property
|
|
//
|
|
// rocky._listeners = {
|
|
// "event_1" : [ function_1, function_2, ... ],
|
|
// "event_2" ; [ ... ] ,
|
|
// ...
|
|
// }
|
|
//
|
|
// please note that we ignore events, no API is interested in by asking each of them
|
|
// via .add_handler(event_name, func)
|
|
|
|
static jerry_value_t prv_event_listener_extract_args(const jerry_length_t argc,
|
|
const jerry_value_t argv[],
|
|
char *event_name, size_t event_name_size,
|
|
jerry_value_t *func) {
|
|
PBL_ASSERTN(event_name && func);
|
|
|
|
if (argc < 2) {
|
|
return rocky_error_arguments_missing();
|
|
}
|
|
|
|
memset(event_name, 0, event_name_size);
|
|
const jerry_size_t len = jerry_string_to_utf8_char_buffer(argv[0],
|
|
(jerry_char_t *)event_name,
|
|
event_name_size);
|
|
if (len == 0 || len == event_name_size) {
|
|
return rocky_error_argument_invalid("Not a valid event");
|
|
}
|
|
|
|
if (!jerry_value_is_function(argv[1])) {
|
|
return rocky_error_argument_invalid("Not a valid handler");
|
|
}
|
|
*func = argv[1];
|
|
|
|
return jerry_create_undefined();
|
|
}
|
|
|
|
JERRY_FUNCTION(prv_add_event_listener) {
|
|
char event_name[32];
|
|
jerry_value_t func; // out parameter &func will not be acquired
|
|
JS_VAR arg_result = prv_event_listener_extract_args(argc, argv,
|
|
event_name, sizeof(event_name),
|
|
&func);
|
|
if (jerry_value_has_error_flag(arg_result)) {
|
|
return jerry_acquire_value(arg_result);
|
|
}
|
|
|
|
bool is_relevant = false;
|
|
API_REFS_FOREACH(api_ref) {
|
|
if ((*api_ref)->add_handler) {
|
|
is_relevant = (*api_ref)->add_handler(event_name, func);
|
|
if (is_relevant) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!is_relevant) {
|
|
APP_LOG(LOG_LEVEL_WARNING, "Unknown event '%s'", event_name);
|
|
return jerry_create_undefined();
|
|
}
|
|
|
|
prv_add_event_listener_to_list((char *)event_name, func);
|
|
|
|
return jerry_create_undefined();
|
|
}
|
|
|
|
JERRY_FUNCTION(prv_remove_event_listener) {
|
|
char event_name[32];
|
|
jerry_value_t func; // out parameter &func will not be acquired
|
|
JS_VAR arg_result = prv_event_listener_extract_args(argc, argv,
|
|
event_name, sizeof(event_name),
|
|
&func);
|
|
if (jerry_value_has_error_flag(arg_result)) {
|
|
return jerry_acquire_value(arg_result);
|
|
}
|
|
|
|
const bool removed = prv_remove_event_listener_from_list(event_name, func);
|
|
if (removed) {
|
|
API_REFS_FOREACH(api_ref) {
|
|
if ((*api_ref)->remove_handler) {
|
|
(*api_ref)->remove_handler(event_name, func);
|
|
}
|
|
}
|
|
} else {
|
|
APP_LOG(LOG_LEVEL_WARNING, "Unknown handler for event '%s'", event_name);
|
|
}
|
|
|
|
return jerry_create_undefined();
|
|
}
|
|
|
|
JERRY_FUNCTION(prv_event_constructor) {
|
|
if (argc < 1) {
|
|
return rocky_error_arguments_missing();
|
|
}
|
|
if (!jerry_value_is_string(argv[0])) {
|
|
return rocky_error_unexpected_type(0, "String");
|
|
}
|
|
jerry_set_object_field(this_val, ROCKY_EVENT_TYPE, argv[0]);
|
|
return jerry_create_undefined();
|
|
}
|
|
|
|
static void prv_copy_property(const jerry_value_t rocky,
|
|
const char *name_from, const char *name_to) {
|
|
JS_VAR on = jerry_get_object_field(rocky, name_from);
|
|
jerry_set_object_field(rocky, name_to, on);
|
|
}
|
|
|
|
void rocky_global_init(const RockyGlobalAPI *const *global_apis) {
|
|
PBL_ASSERTN(global_apis);
|
|
s_global_apis = global_apis;
|
|
|
|
JS_VAR rocky = jerry_create_object();
|
|
// this keeps a permanent reference to the singleton
|
|
rocky_set_rocky_singleton(rocky);
|
|
|
|
rocky_add_function(rocky, ROCKY_ON, prv_add_event_listener);
|
|
prv_copy_property(rocky, ROCKY_ON, ROCKY_ADD_EVENT_LISTENER);
|
|
rocky_add_function(rocky, ROCKY_OFF, prv_remove_event_listener);
|
|
prv_copy_property(rocky, ROCKY_OFF, ROCKY_REMOVE_EVENT_LISTENER);
|
|
|
|
JS_UNUSED_VAL = rocky_add_constructor(ROCKY_EVENT_CONSTRUCTOR, prv_event_constructor);
|
|
|
|
API_REFS_FOREACH(api_ref) {
|
|
if ((*api_ref)->init) {
|
|
(*api_ref)->init();
|
|
}
|
|
}
|
|
}
|
|
|
|
void rocky_global_deinit(void) {
|
|
API_REFS_FOREACH(api_ref) {
|
|
if ((*api_ref)->deinit) {
|
|
(*api_ref)->deinit();
|
|
}
|
|
}
|
|
|
|
#if APPLIB_EMSCRIPTEN
|
|
rocky_delete_singleton();
|
|
#endif
|
|
}
|
|
|
|
static bool prv_has_any(jerry_value_t event_listeners, uint32_t idx,
|
|
jerry_value_t listener, void *data) {
|
|
*(bool *)data = true;
|
|
return false;
|
|
}
|
|
|
|
bool rocky_global_has_event_handlers(const char *event_name) {
|
|
bool result = false;
|
|
prv_iterate_event_listeners(event_name, prv_has_any, &result);
|
|
return result;
|
|
}
|
|
|
|
typedef struct {
|
|
const jerry_value_t this_arg;
|
|
const jerry_value_t *args_p;
|
|
jerry_size_t args_count;
|
|
} EventHandlersCallArgs;
|
|
|
|
static bool prv_call_event_handlers_cb(jerry_value_t event_listeners, uint32_t idx,
|
|
jerry_value_t listener, void *data) {
|
|
EventHandlersCallArgs * const args = data;
|
|
rocky_util_call_user_function_and_log_uncaught_error(listener, args->this_arg, args->args_p,
|
|
args->args_count);
|
|
return true;
|
|
}
|
|
|
|
void rocky_global_call_event_handlers(jerry_value_t event) {
|
|
EventHandlersCallArgs args = {
|
|
.this_arg = jerry_create_undefined(),
|
|
.args_p = &event,
|
|
.args_count = 1,
|
|
};
|
|
|
|
JS_VAR event_str = jerry_get_object_field(event, "type");
|
|
char *event_name = rocky_string_alloc_and_copy(event_str);
|
|
prv_iterate_event_listeners(event_name, prv_call_event_handlers_cb, &args);
|
|
|
|
task_free(event_name);
|
|
jerry_release_value(args.this_arg);
|
|
}
|
|
|
|
static void prv_call_event_handlers_async_cb(void *ctx) {
|
|
jerry_value_t event = (jerry_value_t)(uintptr_t) ctx;
|
|
rocky_global_call_event_handlers(event);
|
|
jerry_release_value(event); // was acquired in rocky_global_call_event_handlers_async() call
|
|
}
|
|
|
|
void rocky_global_call_event_handlers_async(jerry_value_t event) {
|
|
sys_current_process_schedule_callback(prv_call_event_handlers_async_cb,
|
|
(void *)(uintptr_t)jerry_acquire_value(event));
|
|
}
|
|
|
|
jerry_value_t rocky_global_create_event(const char *type_str) {
|
|
JS_VAR jerry_type_str = jerry_create_string_utf8((const jerry_char_t *)type_str);
|
|
JS_VAR event = rocky_create_with_constructor(ROCKY_EVENT_CONSTRUCTOR, &jerry_type_str, 1);
|
|
return jerry_acquire_value(event);
|
|
}
|