/* ** Copyright (C) 2001-2024 Zabbix SIA ** ** This program is free software: you can redistribute it and/or modify it under the terms of ** the GNU Affero General Public License as published by the Free Software Foundation, version 3. ** ** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; ** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ** See the GNU Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License along with this program. ** If not, see <https://www.gnu.org/licenses/>. **/ #include "embed.h" #include "duktape.h" #include "zbxembed.h" #ifdef HAVE_LIBCURL #include "browser_alert.h" #include "browser_element.h" #include "browser_error.h" #include "browser_perf.h" #include "webdriver.h" #include "zbxalgo.h" #include "zbxcommon.h" #include "zbxjson.h" #include "zbxtime.h" #include "zbxtypes.h" #include "zbxvariant.h" #include "zbxstr.h" /****************************************************************************** * * * Purpose: return backing C structure embedded in browser object * * * ******************************************************************************/ static zbx_webdriver_t *es_webdriver(duk_context *ctx) { zbx_webdriver_t *wd; zbx_es_env_t *env; void *objptr; if (NULL == (env = zbx_es_get_env(ctx))) { (void)duk_push_error_object(ctx, DUK_RET_EVAL_ERROR, "cannot access internal environment"); return NULL; } duk_push_this(ctx); objptr = duk_require_heapptr(ctx, -1); duk_pop(ctx); if (NULL == (wd = (zbx_webdriver_t *)es_obj_get_data(env, objptr, ES_OBJ_BROWSER))) (void)duk_push_error_object(ctx, DUK_RET_EVAL_ERROR, "cannot find native data attached to object"); return wd; } /****************************************************************************** * * * Purpose: browser destructor * * * ******************************************************************************/ static duk_ret_t es_browser_dtor(duk_context *ctx) { zbx_webdriver_t *wd; zbx_es_env_t *env; if (NULL == (env = zbx_es_get_env(ctx))) return duk_error(ctx, DUK_RET_TYPE_ERROR, "cannot access internal environment"); zabbix_log(LOG_LEVEL_TRACE, "Browser::~Browser()"); if (NULL != (wd = (zbx_webdriver_t *)es_obj_detach_data(env, duk_require_heapptr(ctx, -1), ES_OBJ_BROWSER))) { webdriver_release(wd); env->browser_objects--; } return 0; } /****************************************************************************** * * * Purpose: browser constructor * * * ******************************************************************************/ static duk_ret_t es_browser_ctor(duk_context *ctx) { #define BROWSER_INSTANCE_LIMIT 4 zbx_webdriver_t *wd = NULL; zbx_es_env_t *env; int err_index = -1; char *error = NULL, *capabilities = NULL; void *objptr = NULL; if (!duk_is_constructor_call(ctx)) return DUK_RET_TYPE_ERROR; if (NULL == (env = zbx_es_get_env(ctx))) { err_index = duk_push_error_object(ctx, DUK_RET_TYPE_ERROR, "cannot access internal environment"); goto out; } duk_push_heapptr(ctx, env->json_stringify); duk_dup(ctx, 0); duk_pcall(ctx, 1); if (SUCCEED != es_duktape_string_decode(duk_safe_to_string(ctx, -1), &capabilities)) { err_index = duk_push_error_object(ctx, DUK_RET_TYPE_ERROR, "cannot convert browser capabilities to utf8"); goto out; } zabbix_log(LOG_LEVEL_TRACE, "Browser::Browser(%s)", capabilities); if (NULL == (env = zbx_es_get_env(ctx))) { err_index = duk_push_error_object(ctx, DUK_RET_TYPE_ERROR, "cannot access internal environment"); goto out; } if (BROWSER_INSTANCE_LIMIT <= env->browser_objects) { err_index = duk_push_error_object(ctx, DUK_RET_TYPE_ERROR, "maximum count of Browser objects was reached"); goto out; } if (NULL == (wd = webdriver_create(env->browser_endpoint, env->config_source_ip, &error))) { err_index = duk_push_error_object(ctx, DUK_RET_TYPE_ERROR, "cannot create webdriver: %s", error); goto out; } wd->env = env; duk_push_this(ctx); objptr = duk_require_heapptr(ctx, -1); es_obj_attach_data(env, objptr, wd, ES_OBJ_BROWSER); wd->browser = duk_get_heapptr(ctx, -1); if (SUCCEED != webdriver_open_session(wd, capabilities, &error)) { err_index = duk_push_error_object(ctx, DUK_RET_TYPE_ERROR, "cannot open webriver session: %s", error); goto out; } duk_push_c_function(ctx, es_browser_dtor, 1); duk_set_finalizer(ctx, -2); out: zbx_free(capabilities); zbx_free(error); if (-1 != err_index) { if (NULL != wd) { (void)es_obj_detach_data(env, objptr, ES_OBJ_BROWSER); webdriver_release(wd); } return duk_throw(ctx); } else env->browser_objects++; return 0; #undef BROWSER_INSTANCE_LIMIT } /****************************************************************************** * * * Purpose: open URL * * * * Stack: 0 - url to open (string) * * * ******************************************************************************/ static duk_ret_t es_browser_navigate(duk_context *ctx) { zbx_webdriver_t *wd; char *error = NULL, *url = NULL; int ret; const char *url_cesu; url_cesu = duk_safe_to_string(ctx, 0); if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); if (SUCCEED != es_duktape_string_decode(url_cesu, &url)) { (void)browser_push_error(ctx, wd, "cannot get url: %s", error); return duk_throw(ctx); } ret = webdriver_url(wd, url, &error); zbx_free(url); if (SUCCEED != ret) { (void)browser_push_error(ctx, wd, "cannot open url: %s", error); zbx_free(error); return duk_throw(ctx); } return 0; } /****************************************************************************** * * * Purpose: get currently opened URL * * * * Return value: URL (string) * * * ******************************************************************************/ static duk_ret_t es_browser_get_url(duk_context *ctx) { zbx_webdriver_t *wd; char *error = NULL, *url = NULL; if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); if (SUCCEED != webdriver_get_url(wd, &url, &error)) { (void)browser_push_error(ctx, wd, "cannot get url: %s", error); zbx_free(error); return duk_throw(ctx); } es_push_result_string(ctx, url, strlen(url)); zbx_free(url); return 1; } /****************************************************************************** * * * Purpose: find element * * * * Stack: 0 - strategy (string) * * 1 - selector (string) * * * * Return value: Element object if found or null * * * ******************************************************************************/ static duk_ret_t es_browser_find_element(duk_context *ctx) { zbx_webdriver_t *wd; char *error = NULL, *strategy = NULL, *selector = NULL, *element = NULL; int err_index = -1; const char *strategy_cesu, *selector_cesu; strategy_cesu = duk_safe_to_string(ctx, 0); selector_cesu = duk_safe_to_string(ctx, 1); if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); if (SUCCEED != es_duktape_string_decode(strategy_cesu, &strategy)) { err_index = browser_push_error(ctx, wd, "cannot convert strategy parameter to utf8"); goto out; } if (SUCCEED != es_duktape_string_decode(selector_cesu, &selector)) { err_index = browser_push_error(ctx, wd, "cannot convert selector parameter to utf8"); goto out; } if (SUCCEED != webdriver_find_element(wd, strategy, selector, &element, &error)) { err_index = browser_push_error(ctx, wd, "cannot find element: %s", error); zbx_free(error); goto out; } if (NULL != element) { wd_element_create(ctx, wd, element); zbx_free(element); } else duk_push_null(ctx); out: zbx_free(strategy); zbx_free(selector); if (-1 != err_index) return duk_throw(ctx); return 1; } /****************************************************************************** * * * Purpose: find elements * * * * Stack: 0 - strategy (string) * * 1 - selector (string) * * * * Return value: array of Element objects. The array can be empty if no * * elements matching specified strategy and selector were found * * * ******************************************************************************/ static duk_ret_t es_browser_find_elements(duk_context *ctx) { zbx_webdriver_t *wd; char *error = NULL, *strategy = NULL, *selector = NULL; int err_index = -1; zbx_vector_str_t elements; const char *strategy_cesu, *selector_cesu; strategy_cesu = duk_safe_to_string(ctx, 0); selector_cesu = duk_safe_to_string(ctx, 1); zbx_vector_str_create(&elements); if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); if (SUCCEED != es_duktape_string_decode(strategy_cesu, &strategy)) { err_index = browser_push_error(ctx, wd, "cannot convert strategy parameter to utf8"); goto out; } if (SUCCEED != es_duktape_string_decode(selector_cesu, &selector)) { err_index = browser_push_error(ctx, wd, "cannot convert selector parameter to utf8"); goto out; } if (SUCCEED != webdriver_find_elements(wd, strategy, selector, &elements, &error)) { err_index = browser_push_error(ctx, wd, "cannot find element: %s", error); zbx_free(error); goto out; } wd_element_create_array(ctx, wd, &elements); out: zbx_vector_str_clear_ext(&elements, zbx_str_free); zbx_vector_str_destroy(&elements); zbx_free(strategy); zbx_free(selector); if (-1 != err_index) return duk_throw(ctx); return 1; } /****************************************************************************** * * * Purpose: get variant value * * * * Return value: value matching the variant type * * * ******************************************************************************/ static void es_browser_get_variant(zbx_es_env_t *env, duk_context *ctx, const zbx_variant_t *var) { const char *str; duk_idx_t idx; zbx_uint32_t len; switch (var->type) { case ZBX_VARIANT_NONE: duk_push_null(ctx); break; case ZBX_VARIANT_STR: duk_push_string(ctx, var->data.str); break; case ZBX_VARIANT_UI64: duk_push_number(ctx, (double)var->data.ui64); break; case ZBX_VARIANT_DBL: duk_push_number(ctx, var->data.dbl); break; case ZBX_VARIANT_BIN: len = zbx_variant_data_bin_get(var->data.bin, (const void **)&str); duk_push_heapptr(ctx, env->json_parse); duk_push_lstring(ctx, str, len); duk_pcall(ctx, 1); break; case ZBX_VARIANT_VECTOR: idx = duk_push_array(ctx); for (int i = 0; i < var->data.vector->values_num; i++) { es_browser_get_variant(env, ctx, &var->data.vector->values[i]); duk_put_prop_index(ctx, idx, (duk_uarridx_t)i); } break; case ZBX_VARIANT_ERR: duk_push_string(ctx, var->data.err); break; } } /****************************************************************************** * * * Purpose: push performance entry object on stack * * * ******************************************************************************/ static void es_browser_push_performance_entry(zbx_es_env_t *env, duk_context *ctx, zbx_wd_perf_entry_t *entry) { zbx_hashset_iter_t iter; zbx_wd_attr_t *attr; duk_idx_t idx; idx = duk_push_object(ctx); zbx_hashset_iter_reset(&entry->attrs, &iter); while (NULL != (attr = (zbx_wd_attr_t *)zbx_hashset_iter_next(&iter))) { es_browser_get_variant(env, ctx, &attr->value); duk_put_prop_string(ctx, idx, attr->name); } } /****************************************************************************** * * * Purpose: push error object on stack * * * ******************************************************************************/ static void es_browser_push_error(duk_context *ctx, zbx_webdriver_t *wd) { const char *error_code; int http_status; duk_push_object(ctx); if (NULL != wd->error) { error_code = wd->error->error; http_status = wd->error->http_code; } else { error_code = ""; http_status = 0; } duk_push_number(ctx, http_status); duk_put_prop_string(ctx, -2, "http_status"); duk_push_string(ctx, error_code); duk_put_prop_string(ctx, -2, "code"); es_push_result_string(ctx, wd->last_error_message, strlen(wd->last_error_message)); duk_put_prop_string(ctx, -2, "message"); } /****************************************************************************** * * * Purpose: get browser result * * * * Return value: result object * * * ******************************************************************************/ static duk_ret_t es_browser_get_result(duk_context *ctx) { zbx_webdriver_t *wd; duk_idx_t idx_result, idx_perf, idx_details, idx_summary, idx_marks; if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); idx_result = duk_push_object(ctx); duk_push_number(ctx, zbx_time() - wd->create_time); duk_put_prop_string(ctx, -2, "duration"); if (SUCCEED == webdriver_has_error(wd)) { es_browser_push_error(ctx, wd); duk_put_prop_string(ctx, idx_result, "error"); } if (0 < wd->perf.details.values_num) { idx_perf = duk_push_object(ctx); idx_details = duk_push_array(ctx); for (int i = 0; i < wd->perf.details.values_num; i++) { duk_idx_t idx; zbx_wd_perf_details_t *details = &wd->perf.details.values[i]; idx = duk_push_object(ctx); for (int j = 0; j < wd->perf.bookmarks.values_num; j++) { zbx_wd_perf_bookmark_t *bookmark = &wd->perf.bookmarks.values[i]; if (bookmark->details == details) { duk_push_string(ctx, bookmark->name); duk_put_prop_string(ctx, idx, "mark"); break; } } if (NULL != details->navigation) { es_browser_push_performance_entry(wd->env, ctx, details->navigation); duk_put_prop_string(ctx, idx, "navigation"); } es_browser_push_performance_entry(wd->env, ctx, details->resource); duk_put_prop_string(ctx, idx, "resource"); if (0 != details->user.values_num) { duk_idx_t idx_user; idx_user = duk_push_array(ctx); for (int j = 0; j < details->user.values_num; j++) { es_browser_push_performance_entry(wd->env, ctx, details->user.values[j]); duk_put_prop_index(ctx, idx_user, (duk_uarridx_t)j); } duk_put_prop_string(ctx, idx, "user"); } duk_put_prop_index(ctx, idx_details, (duk_uarridx_t)i); } duk_put_prop_string(ctx, idx_perf, "details"); idx_summary = duk_push_object(ctx); es_browser_push_performance_entry(wd->env, ctx, wd->perf.navigation_summary); duk_put_prop_string(ctx, idx_summary, "navigation"); es_browser_push_performance_entry(wd->env, ctx, wd->perf.resource_summary); duk_put_prop_string(ctx, idx_summary, "resource"); duk_put_prop_string(ctx, idx_perf, "summary"); idx_marks = duk_push_array(ctx); for (int i = 0; i < wd->perf.bookmarks.values_num; i++) { duk_idx_t idx; zbx_wd_perf_bookmark_t *bookmark = &wd->perf.bookmarks.values[i]; idx = duk_push_object(ctx); duk_push_string(ctx, bookmark->name); duk_put_prop_string(ctx, idx, "name"); duk_push_number(ctx, (double)i); duk_put_prop_string(ctx, idx, "index"); duk_put_prop_index(ctx, idx_marks, (duk_uarridx_t)i); } duk_put_prop_string(ctx, idx_perf, "marks"); duk_put_prop_string(ctx, idx_result, "performance_data"); } return 1; } /****************************************************************************** * * * Purpose: set script timeout * * * ******************************************************************************/ static duk_ret_t es_browser_set_script_timeout(duk_context *ctx) { zbx_webdriver_t *wd; char *error = NULL; int timeout; timeout = duk_get_int(ctx, 0); if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); if (SUCCEED != webdriver_set_timeouts(wd, timeout, -1, -1, &error)) { (void)browser_push_error(ctx, wd, "cannot set script timeout: %s", error); zbx_free(error); return duk_throw(ctx); } return 0; } /****************************************************************************** * * * Purpose: set script timeout * * * ******************************************************************************/ static duk_ret_t es_browser_set_session_timeout(duk_context *ctx) { zbx_webdriver_t *wd; char *error = NULL; int timeout; timeout = duk_get_int(ctx, 0); if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); if (SUCCEED != webdriver_set_timeouts(wd, -1, timeout, -1, &error)) { (void)browser_push_error(ctx, wd, "cannot set page load timeout timeout: %s", error); zbx_free(error); return duk_throw(ctx); } return 0; } /****************************************************************************** * * * Purpose: set script timeout * * * ******************************************************************************/ static duk_ret_t es_browser_set_element_wait_timeout(duk_context *ctx) { zbx_webdriver_t *wd; char *error = NULL; int timeout; timeout = duk_get_int(ctx, 0); if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); if (SUCCEED != webdriver_set_timeouts(wd, -1, -1, timeout, &error)) { (void)browser_push_error(ctx, wd, "cannot set implicit timeout: %s", error); zbx_free(error); return duk_throw(ctx); } return 0; } /****************************************************************************** * * * Purpose: get cookies * * * * Return value: array of Cookie objects * * * ******************************************************************************/ static duk_ret_t es_browser_get_cookies(duk_context *ctx) { zbx_webdriver_t *wd; char *error = NULL; int err_index = -1; char *cookies = NULL; if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); if (SUCCEED != webdriver_get_cookies(wd, &cookies, &error)) { err_index = browser_push_error(ctx, wd, "cannot get cookies: %s", error); zbx_free(error); goto out; } duk_push_heapptr(ctx, wd->env->json_parse); es_push_result_string(ctx, cookies, strlen(cookies)); duk_pcall(ctx, 1); out: zbx_free(cookies); if (-1 != err_index) return duk_throw(ctx); return 1; } /****************************************************************************** * * * Purpose: add cookie * * * * Stack 0 - Cookie object * * * ******************************************************************************/ static duk_ret_t es_browser_add_cookie(duk_context *ctx) { zbx_webdriver_t *wd; char *error = NULL, *cookie_json = NULL; int err_index = -1; const char *cookie_cesu; if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); duk_push_heapptr(ctx, wd->env->json_stringify); duk_dup(ctx, 0); duk_pcall(ctx, 1); cookie_cesu = duk_safe_to_string(ctx, -1); /* to be sure that the object is not freed during argument evaluation - acquire it again */ if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); if (SUCCEED != es_duktape_string_decode(cookie_cesu, &cookie_json)) { (void)browser_push_error(ctx, wd, "cannot convert cookie object to JSON format"); return duk_throw(ctx); } if (SUCCEED != webdriver_add_cookie(wd, cookie_json, &error)) { err_index = browser_push_error(ctx, wd, "cannot open url: %s", error); zbx_free(error); } zbx_free(cookie_json); if (-1 != err_index) return duk_throw(ctx); return 0; } /****************************************************************************** * * * Purpose: configure automatic screenshot taking functionality * * * * Return value: base64 encoded screenshot (string) * * * ******************************************************************************/ static duk_ret_t es_browser_get_screenshot(duk_context *ctx) { zbx_webdriver_t *wd; char *screenshot = NULL, *error = NULL; if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); if (SUCCEED != webdriver_get_screenshot(wd, &screenshot, &error)) { (void) browser_push_error(ctx, wd, "cannot capture screenshot: %s", error); zbx_free(error); return duk_throw(ctx); } es_push_result_string(ctx, screenshot, strlen(screenshot)); zbx_free(screenshot); return 1; } /****************************************************************************** * * * Purpose: set browser screen size * * * * Stack 0 - width * * 1 - height * * * ******************************************************************************/ static duk_ret_t es_browser_set_screen_size(duk_context *ctx) { zbx_webdriver_t *wd; int width, height; char *error = NULL; width = duk_get_int(ctx, 0); height = duk_get_int(ctx, 1); if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); if (0 > width || width > 8192 || 0 > height || height > 8192) { (void)browser_push_error(ctx, wd, "unsupported screen size"); return duk_throw(ctx); } if (SUCCEED != webdriver_set_screen_size(wd, width, height, &error)) { browser_push_error(ctx, wd, "cannot set screen size: %s", error); zbx_free(error); return duk_throw(ctx); } return 0; } /****************************************************************************** * * * Purpose: get browser error * * * ******************************************************************************/ static duk_ret_t es_browser_get_error(duk_context *ctx) { zbx_webdriver_t *wd; if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); if (SUCCEED == webdriver_has_error(wd)) es_browser_push_error(ctx, wd); else duk_push_null(ctx); return 1; } /****************************************************************************** * * * Purpose: discard browser error * * * ******************************************************************************/ static duk_ret_t es_browser_discard_error(duk_context *ctx) { zbx_webdriver_t *wd; if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); webdriver_discard_error(wd); return 0; } /****************************************************************************** * * * Purpose: set custom error * * * * Stack 0 - script * * * ******************************************************************************/ static duk_ret_t es_browser_set_error(duk_context *ctx) { zbx_webdriver_t *wd; char *message = NULL; const char *message_cesu; message_cesu = duk_safe_to_string(ctx, 0); if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); if (SUCCEED != es_duktape_string_decode(message_cesu, &message)) { (void)browser_push_error(ctx, wd, "cannot convert message parameter to utf8"); return duk_throw(ctx); } webdriver_set_error(wd, message); return 0; } /****************************************************************************** * * * Purpose: get opened page source * * * * Return value: page source (string) * * * ******************************************************************************/ static duk_ret_t es_browser_get_page_source(duk_context *ctx) { zbx_webdriver_t *wd; char *source = NULL, *error = NULL; if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); if (SUCCEED != webdriver_get_page_source(wd, &source, &error)) { (void) browser_push_error(ctx, wd, "cannot get page source: %s", error); zbx_free(error); return duk_throw(ctx); } es_push_result_string(ctx, source, strlen(source)); zbx_free(source); return 1; } /****************************************************************************** * * * Purpose: get alert * * * * Return value: Alert object if found or null * * * ******************************************************************************/ static duk_ret_t es_browser_get_alert(duk_context *ctx) { zbx_webdriver_t *wd; char *error = NULL, *alert = NULL; int err_index = -1; if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); if (SUCCEED != webdriver_get_alert(wd, &alert, &error)) { err_index = browser_push_error(ctx, wd, "cannot get alert: %s", error); zbx_free(error); goto out; } if (NULL != alert) { zbx_replace_invalid_utf8(alert); wd_alert_create(ctx, wd, alert); zbx_free(alert); } else duk_push_null(ctx); out: if (-1 != err_index) return duk_throw(ctx); return 1; } /****************************************************************************** * * * Purpose: collect performance data * * * * Stack 0 - bookmark (string/null, optional) * * * ******************************************************************************/ static duk_ret_t es_browser_collect_perf_entries(duk_context *ctx) { int err_index = -1; zbx_webdriver_t *wd; char *bookmark = NULL, *error = NULL; const char *bookmark_str = NULL; if (!duk_is_null(ctx, 0) && !duk_is_undefined(ctx, 0)) bookmark_str = duk_safe_to_string(ctx, 0); if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); if (NULL != bookmark_str && SUCCEED != es_duktape_string_decode(bookmark_str, &bookmark)) { err_index = browser_push_error(ctx, wd, "cannot convert bookmark parameter to utf8"); goto out; } if (SUCCEED != webdriver_collect_perf_data(wd, bookmark, &error)) { err_index = browser_push_error(ctx, wd, "cannot collect performance data: %s", error); zbx_free(error); } zbx_free(bookmark); out: if (-1 != err_index) return duk_throw(ctx); return 0; } /****************************************************************************** * * * Purpose: get raw performance data * * * * Return value: array of performance entry objects * * * ******************************************************************************/ static duk_ret_t es_browser_get_raw_perf_entries(duk_context *ctx) { zbx_webdriver_t *wd; char *error = NULL, *result = NULL; struct zbx_json_parse jp; if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); if (SUCCEED != webdriver_get_raw_perf_data(wd, NULL, &jp, &error)) { (void)browser_push_error(ctx, wd, "cannot get performance data: %s", error); zbx_free(error); return duk_throw(ctx); } duk_push_heapptr(ctx, wd->env->json_parse); result = zbx_substr(wd->data, jp.start - wd->data, jp.end - wd->data); zbx_replace_invalid_utf8(result); es_push_result_string(ctx, result, strlen(result)); zbx_free(result); duk_pcall(ctx, 1); return 1; } /****************************************************************************** * * * Purpose: get performance data by type * * * * Stack 0 - performance entry type * * * * Return value: array of performance entry objects * * * ******************************************************************************/ static duk_ret_t es_browser_get_raw_perf_entries_by_type(duk_context *ctx) { zbx_webdriver_t *wd; char *error = NULL, *entry_type = NULL; struct zbx_json_parse jp; int err_index = -1; const char *type_cesu; if (duk_is_null(ctx, 0) || duk_is_undefined(ctx, 0)) { if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); (void)browser_push_error(ctx, wd, "missing entry type parameter"); return duk_throw(ctx); } type_cesu = duk_safe_to_string(ctx, 0); if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); if (SUCCEED != es_duktape_string_decode(type_cesu, &entry_type)) { (void)browser_push_error(ctx, wd, "cannot convert entry type parameter to utf8"); return duk_throw(ctx); } if (SUCCEED != webdriver_get_raw_perf_data(wd, entry_type, &jp, &error)) { err_index = browser_push_error(ctx, wd, "cannot get performance data: %s", error); zbx_free(error); } else { char *result = NULL; duk_push_heapptr(ctx, wd->env->json_parse); result = zbx_substr(wd->data, jp.start - wd->data, jp.end - wd->data); zbx_replace_invalid_utf8(result); es_push_result_string(ctx, result, strlen(result)); zbx_free(result); duk_pcall(ctx, 1); } zbx_free(entry_type); if (-1 != err_index) return duk_throw(ctx); return 1; } /****************************************************************************** * * * Purpose: get browser element id * * * * Parameters: ctx - [IN] * * idx - [IN] element object index on stack * * * * Return value: Allocated element id string or NULL * * * ******************************************************************************/ static char *es_browser_get_element_id(duk_context *ctx, duk_idx_t idx) { zbx_es_env_t *env; void *el; if (duk_get_type(ctx, 0) != DUK_TYPE_OBJECT) return NULL; if (NULL == (env = zbx_es_get_env(ctx))) return NULL; if (NULL == (el = es_obj_get_data(env, duk_require_heapptr(ctx, idx), ES_OBJ_ELEMENT))) return NULL; return zbx_strdup(NULL, wd_element_get_id(el)); } /****************************************************************************** * * * Purpose: set custom error * * * * Stack 0 - script * * * ******************************************************************************/ static duk_ret_t es_browser_switch_frame(duk_context *ctx) { zbx_webdriver_t *wd; char *frame = NULL, *error = NULL; int err_index = -1; if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); if (!duk_is_null(ctx, 0) && !duk_is_undefined(ctx, 0)) { if (duk_get_type(ctx, 0) == DUK_TYPE_NUMBER) { frame = zbx_dsprintf(NULL, "%.0f", duk_get_number(ctx, 0)); } else if (NULL == (frame = es_browser_get_element_id(ctx, 0))) { (void)browser_push_error(ctx, wd, "invalid parameter"); return duk_throw(ctx); } } if (SUCCEED != webdriver_switch_frame(wd, frame, &error)) { err_index = browser_push_error(ctx, wd, "cannot switch frame: %s", error); zbx_free(error); } zbx_free(frame); if (-1 != err_index) return duk_throw(ctx); return 0; } #ifdef BROWSER_EXECUTE_SCRIPT /****************************************************************************** * * * Purpose: execute custom script * * * * Stack 0 - script * * * * Return value: script result * * * ******************************************************************************/ static duk_ret_t es_browser_execute_script(duk_context *ctx) { zbx_webdriver_t *wd; char *script = NULL, *error = NULL; int err_index = -1; struct zbx_json_parse jp; const char *script_cesu; script_cesu = duk_safe_to_string(ctx, 0); if (NULL == (wd = es_webdriver(ctx))) return duk_throw(ctx); if (SUCCEED != es_duktape_string_decode(script_cesu, &script)) { (void)browser_push_error(ctx, wd, "cannot convert script parameter to utf8"); return duk_throw(ctx); } if (SUCCEED != webdriver_execute_script(wd, script, &jp, &error)) { err_index = browser_push_error(ctx, wd, "cannot execute script: %s", error); zbx_free(error); } else { char *result = NULL; size_t result_alloc = 0; if (NULL == zbx_json_decodevalue_dyn(jp.start, &result, &result_alloc, NULL)) { result = (char *)zbx_malloc(NULL, jp.end - jp.start + 2); memcpy(result, jp.start, jp.end - jp.start + 1); result[jp.end - jp.start + 1] = '\0'; } duk_push_string(ctx, result); zbx_free(result); } zbx_free(script); if (-1 != err_index) return duk_throw(ctx); return 1; } #endif static const duk_function_list_entry browser_methods[] = { {"navigate", es_browser_navigate, 1}, {"getUrl", es_browser_get_url, 0}, {"findElement", es_browser_find_element, 2}, {"findElements", es_browser_find_elements, 2}, {"getResult", es_browser_get_result, 0}, {"setScriptTimeout", es_browser_set_script_timeout, 1}, {"setSessionTimeout", es_browser_set_session_timeout, 1}, {"setElementWaitTimeout", es_browser_set_element_wait_timeout, 1}, {"getCookies", es_browser_get_cookies, 0}, {"addCookie", es_browser_add_cookie, 1}, {"getScreenshot", es_browser_get_screenshot, 0}, {"setScreenSize", es_browser_set_screen_size, 2}, {"setError", es_browser_set_error, 1}, {"getError", es_browser_get_error, 0}, {"discardError", es_browser_discard_error, 0}, {"collectPerfEntries", es_browser_collect_perf_entries, 1}, {"getRawPerfEntries", es_browser_get_raw_perf_entries, 0}, {"getRawPerfEntriesByType", es_browser_get_raw_perf_entries_by_type, 1}, {"getPageSource", es_browser_get_page_source, 0}, {"getAlert", es_browser_get_alert, 0}, {"switchFrame", es_browser_switch_frame, 1}, #ifdef BROWSER_EXECUTE_SCRIPT {"executeScript", es_browser_execute_script, 1}, #endif {0} }; #else static duk_ret_t es_browser_ctor(duk_context *ctx) { if (!duk_is_constructor_call(ctx)) return DUK_RET_EVAL_ERROR; return duk_error(ctx, DUK_RET_EVAL_ERROR, "missing cURL library"); } static const duk_function_list_entry browser_methods[] = { {NULL, NULL, 0} }; #endif /****************************************************************************** * * * Purpose: get default chrome browser options * * * * Return value: chrome browser options (object) * * * ******************************************************************************/ static duk_ret_t es_browser_chrome_options(duk_context *ctx) { duk_push_object(ctx); /* {} */ duk_push_object(ctx); /* capabilities */ duk_push_object(ctx); /* alwaysMatch */ duk_push_string(ctx, "chrome"); duk_put_prop_string(ctx, -2, "browserName"); duk_push_string(ctx, "normal"); duk_put_prop_string(ctx, -2, "pageLoadStrategy"); duk_push_object(ctx); /* goog:chromeOptions */ duk_push_array(ctx); /* args */ duk_push_string(ctx, "--headless=new"); duk_put_prop_index(ctx, -2, 0); duk_put_prop_string(ctx, -2, "args"); duk_put_prop_string(ctx, -2, "goog:chromeOptions"); duk_put_prop_string(ctx, -2, "alwaysMatch"); duk_put_prop_string(ctx, -2, "capabilities"); return 1; } /****************************************************************************** * * * Purpose: get default firefox browser options * * * * Return value: firefox browser options (object) * * * ******************************************************************************/ static duk_ret_t es_browser_firefox_options(duk_context *ctx) { duk_push_object(ctx); /* {} */ duk_push_object(ctx); /* capabilities */ duk_push_object(ctx); /* alwaysMatch */ duk_push_string(ctx, "firefox"); duk_put_prop_string(ctx, -2, "browserName"); duk_push_string(ctx, "normal"); duk_put_prop_string(ctx, -2, "pageLoadStrategy"); duk_push_object(ctx); /* moz:firefoxOptions */ duk_push_array(ctx); /* args */ duk_push_string(ctx, "--headless"); duk_put_prop_index(ctx, -2, 0); duk_put_prop_string(ctx, -2, "args"); duk_put_prop_string(ctx, -2, "moz:firefoxOptions"); duk_put_prop_string(ctx, -2, "alwaysMatch"); duk_put_prop_string(ctx, -2, "capabilities"); return 1; } /****************************************************************************** * * * Purpose: get default safari browser options * * * * Return value: safari browser options (object) * * * ******************************************************************************/ static duk_ret_t es_browser_safari_options(duk_context *ctx) { duk_push_object(ctx); duk_push_object(ctx); duk_push_object(ctx); duk_push_string(ctx, "safari"); duk_put_prop_string(ctx, -2, "browserName"); duk_push_string(ctx, "normal"); duk_put_prop_string(ctx, -2, "pageLoadStrategy"); duk_put_prop_string(ctx, -2, "alwaysMatch"); duk_put_prop_string(ctx, -2, "capabilities"); return 1; } /****************************************************************************** * * * Purpose: get default edge browser options * * * * Return value: edge browser options (object) * * * ******************************************************************************/ static duk_ret_t es_browser_edge_options(duk_context *ctx) { duk_push_object(ctx); /* {} */ duk_push_object(ctx); /* capabilities */ duk_push_object(ctx); /* alwaysMatch */ duk_push_string(ctx, "MicrosoftEdge"); duk_put_prop_string(ctx, -2, "browserName"); duk_push_string(ctx, "normal"); duk_put_prop_string(ctx, -2, "pageLoadStrategy"); duk_push_object(ctx); /* ms:edgeOptions */ duk_push_array(ctx); /* args */ duk_push_string(ctx, "--headless=new"); duk_put_prop_index(ctx, -2, 0); duk_put_prop_string(ctx, -2, "args"); duk_put_prop_string(ctx, -2, "ms:edgeOptions"); duk_put_prop_string(ctx, -2, "alwaysMatch"); duk_put_prop_string(ctx, -2, "capabilities"); return 1; } static const duk_function_list_entry browser_static_methods[] = { {"chromeOptions", es_browser_chrome_options, 0}, {"firefoxOptions", es_browser_firefox_options, 0}, {"safariOptions", es_browser_safari_options, 0}, {"edgeOptions", es_browser_edge_options, 0}, {0} }; static int es_browser_create_prototype(duk_context *ctx) { duk_push_c_function(ctx, es_browser_ctor, 1); duk_push_object(ctx); duk_put_function_list(ctx, -1, browser_methods); if (1 != duk_put_prop_string(ctx, -2, "prototype")) return FAIL; duk_put_function_list(ctx, -1, browser_static_methods); if (1 != duk_put_global_string(ctx, "Browser")) return FAIL; return SUCCEED; } /****************************************************************************** * * * Purpose: initialize Browser class * * * ******************************************************************************/ static int es_init_browser(zbx_es_t *es, char **error) { if (0 != setjmp(es->env->loc)) { *error = zbx_strdup(*error, es->env->error); return FAIL; } if (FAIL == es_browser_create_prototype(es->env->ctx)) { *error = zbx_strdup(*error, duk_safe_to_string(es->env->ctx, -1)); duk_pop(es->env->ctx); return FAIL; } #ifdef HAVE_LIBCURL return es_browser_init_errors(es, error); #else return SUCCEED; #endif } /****************************************************************************** * * * Purpose: initialize javascript environment for browser monitoring * * * ******************************************************************************/ int zbx_es_init_browser_env(zbx_es_t *es, const char *endpoint, char **error) { es->env->browser_endpoint = zbx_strdup(NULL, endpoint); /* initialize Browser prototype */ return es_init_browser(es, error); }