| /* |
| * Copyright (C) 2019 Igalia S.L. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library 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 |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "config.h" |
| #include "JSCOptions.h" |
| |
| #include "Options.h" |
| #include <glib/gi18n-lib.h> |
| #include <wtf/Vector.h> |
| #include <wtf/glib/GUniquePtr.h> |
| |
| /** |
| * SECTION: JSCOptions |
| * @short_description: JavaScript options |
| * @title: JSCOptions |
| * |
| * JavaScript options allow changing the behavior of the JavaScript engine. |
| * They affect the way the engine works, so the options must be set |
| * at the very beginning of the program execution, before any other JavaScript |
| * API call. Most of the options are only useful for testing and debugging. |
| * Only a few of them are documented; you can use the undocumented options at |
| * your own risk. (You can find the list of options in the WebKit source code). |
| * |
| * The API allows to set and get any option using the types defined in #JSCOptionType. |
| * You can also iterate all the available options using jsc_options_foreach() and |
| * passing a #JSCOptionsFunc callback. If your application uses #GOptionContext to handle |
| * command line arguments, you can easily integrate the JSCOptions by adding the |
| * #GOptionGroup returned by jsc_options_get_option_group(). |
| * |
| * Since: 2.24 |
| */ |
| |
| using namespace JSC; |
| |
| using int32 = int32_t; |
| using size = size_t; |
| |
| static bool valueFromGValue(const GValue* gValue, bool& value) |
| { |
| value = g_value_get_boolean(gValue); |
| return true; |
| } |
| |
| static void valueToGValue(bool value, GValue* gValue) |
| { |
| g_value_set_boolean(gValue, value); |
| } |
| |
| static bool valueFromGValue(const GValue* gValue, int32_t& value) |
| { |
| value = g_value_get_int(gValue); |
| return true; |
| } |
| |
| static void valueToGValue(int32_t value, GValue* gValue) |
| { |
| g_value_set_int(gValue, value); |
| } |
| |
| #if CPU(ADDRESS64) |
| static bool valueFromGValue(const GValue* gValue, unsigned& value) |
| { |
| value = g_value_get_uint(gValue); |
| return true; |
| } |
| |
| static void valueToGValue(unsigned value, GValue* gValue) |
| { |
| g_value_set_uint(gValue, value); |
| } |
| #endif |
| |
| static bool valueFromGValue(const GValue* gValue, size_t& value) |
| { |
| value = GPOINTER_TO_SIZE(g_value_get_pointer(gValue)); |
| return true; |
| } |
| |
| static void valueToGValue(size_t value, GValue* gValue) |
| { |
| g_value_set_pointer(gValue, GSIZE_TO_POINTER(value)); |
| } |
| |
| static bool valueFromGValue(const GValue* gValue, const char*& value) |
| { |
| value = g_value_dup_string(gValue); |
| return true; |
| } |
| |
| static void valueToGValue(const char* value, GValue* gValue) |
| { |
| g_value_set_string(gValue, value); |
| } |
| |
| static bool valueFromGValue(const GValue* gValue, double& value) |
| { |
| value = g_value_get_double(gValue); |
| return true; |
| } |
| |
| static void valueToGValue(double value, GValue* gValue) |
| { |
| g_value_set_double(gValue, value); |
| } |
| |
| static bool valueFromGValue(const GValue* gValue, OptionRange& value) |
| { |
| return value.init(g_value_get_string(gValue) ? g_value_get_string(gValue) : "<null>"); |
| } |
| |
| static void valueToGValue(const OptionRange& value, GValue* gValue) |
| { |
| const char* rangeString = value.rangeString(); |
| g_value_set_string(gValue, !g_strcmp0(rangeString, "<null>") ? nullptr : rangeString); |
| } |
| |
| static bool valueFromGValue(const GValue* gValue, GCLogging::Level& value) |
| { |
| switch (g_value_get_uint(gValue)) { |
| case 0: |
| value = GCLogging::Level::None; |
| return true; |
| case 1: |
| value = GCLogging::Level::Basic; |
| return true; |
| case 2: |
| value = GCLogging::Level::Verbose; |
| return true; |
| default: |
| break; |
| } |
| |
| return false; |
| } |
| |
| static void valueToGValue(GCLogging::Level value, GValue* gValue) |
| { |
| switch (value) { |
| case GCLogging::Level::None: |
| g_value_set_uint(gValue, 0); |
| break; |
| case GCLogging::Level::Basic: |
| g_value_set_uint(gValue, 1); |
| break; |
| case GCLogging::Level::Verbose: |
| g_value_set_uint(gValue, 2); |
| break; |
| } |
| } |
| |
| static bool valueFromGValue(const GValue* gValue, OSLogType& value) |
| { |
| unsigned optionValue = g_value_get_uint(gValue); |
| if (optionValue > static_cast<unsigned>(OSLogType::Fault)) |
| return false; |
| value = static_cast<OSLogType>(optionValue); |
| return true; |
| } |
| |
| static void valueToGValue(OSLogType value, GValue* gValue) |
| { |
| g_value_set_uint(gValue, static_cast<unsigned>(value)); |
| } |
| |
| static gboolean jscOptionsSetValue(const char* option, const GValue* value) |
| { |
| #define SET_OPTION_VALUE(type_, name_, defaultValue_, availability_, description_) \ |
| if (!g_strcmp0(#name_, option)) { \ |
| OptionsStorage::type_ valueToSet; \ |
| if (!valueFromGValue(value, valueToSet)) \ |
| return FALSE; \ |
| Options::name_() = valueToSet; \ |
| return TRUE; \ |
| } |
| |
| Options::initialize(); |
| FOR_EACH_JSC_OPTION(SET_OPTION_VALUE) |
| #undef SET_OPTION_VALUE |
| |
| return FALSE; |
| } |
| |
| static gboolean jscOptionsGetValue(const char* option, GValue* value) |
| { |
| #define GET_OPTION_VALUE(type_, name_, defaultValue_, availability_, description_) \ |
| if (!g_strcmp0(#name_, option)) { \ |
| OptionsStorage::type_ valueToGet = Options::name_(); \ |
| valueToGValue(valueToGet, value); \ |
| return TRUE; \ |
| } |
| |
| Options::initialize(); |
| FOR_EACH_JSC_OPTION(GET_OPTION_VALUE) |
| #undef GET_OPTION_VALUE |
| |
| return FALSE; |
| } |
| |
| /** |
| * jsc_options_set_boolean: |
| * @option: the option identifier |
| * @value: the value to set |
| * |
| * Set @option as a #gboolean value. |
| * |
| * Returns: %TRUE if option was correctly set or %FALSE otherwise. |
| * |
| * Since: 2.24 |
| */ |
| gboolean jsc_options_set_boolean(const char* option, gboolean value) |
| { |
| g_return_val_if_fail(option, FALSE); |
| |
| GValue gValue = G_VALUE_INIT; |
| g_value_init(&gValue, G_TYPE_BOOLEAN); |
| g_value_set_boolean(&gValue, value); |
| return jscOptionsSetValue(option, &gValue); |
| } |
| |
| /** |
| * jsc_options_get_boolean: |
| * @option: the option identifier |
| * @value: (out): return location for the option value |
| * |
| * Get @option as a #gboolean value. |
| * |
| * Returns: %TRUE if @value has been set or %FALSE if the option doesn't exist |
| * |
| * Since: 2.24 |
| */ |
| gboolean jsc_options_get_boolean(const char* option, gboolean* value) |
| { |
| g_return_val_if_fail(option, FALSE); |
| g_return_val_if_fail(value, FALSE); |
| |
| GValue gValue = G_VALUE_INIT; |
| g_value_init(&gValue, G_TYPE_BOOLEAN); |
| if (!jscOptionsGetValue(option, &gValue)) |
| return FALSE; |
| |
| *value = g_value_get_boolean(&gValue); |
| return TRUE; |
| } |
| |
| /** |
| * jsc_options_set_int: |
| * @option: the option identifier |
| * @value: the value to set |
| * |
| * Set @option as a #gint value. |
| * |
| * Returns: %TRUE if option was correctly set or %FALSE otherwise. |
| * |
| * Since: 2.24 |
| */ |
| gboolean jsc_options_set_int(const char* option, gint value) |
| { |
| g_return_val_if_fail(option, FALSE); |
| |
| GValue gValue = G_VALUE_INIT; |
| g_value_init(&gValue, G_TYPE_INT); |
| g_value_set_int(&gValue, value); |
| return jscOptionsSetValue(option, &gValue); |
| } |
| |
| /** |
| * jsc_options_get_int: |
| * @option: the option identifier |
| * @value: (out): return location for the option value |
| * |
| * Get @option as a #gint value. |
| * |
| * Returns: %TRUE if @value has been set or %FALSE if the option doesn't exist |
| * |
| * Since: 2.24 |
| */ |
| gboolean jsc_options_get_int(const char* option, gint* value) |
| { |
| g_return_val_if_fail(option, FALSE); |
| g_return_val_if_fail(value, FALSE); |
| |
| GValue gValue = G_VALUE_INIT; |
| g_value_init(&gValue, G_TYPE_INT); |
| if (!jscOptionsGetValue(option, &gValue)) |
| return FALSE; |
| |
| *value = g_value_get_int(&gValue); |
| return TRUE; |
| } |
| |
| /** |
| * jsc_options_set_uint: |
| * @option: the option identifier |
| * @value: the value to set |
| * |
| * Set @option as a #guint value. |
| * |
| * Returns: %TRUE if option was correctly set or %FALSE otherwise. |
| * |
| * Since: 2.24 |
| */ |
| gboolean jsc_options_set_uint(const char* option, guint value) |
| { |
| g_return_val_if_fail(option, FALSE); |
| |
| GValue gValue = G_VALUE_INIT; |
| g_value_init(&gValue, G_TYPE_UINT); |
| g_value_set_uint(&gValue, value); |
| return jscOptionsSetValue(option, &gValue); |
| } |
| |
| /** |
| * jsc_options_get_uint: |
| * @option: the option identifier |
| * @value: (out): return location for the option value |
| * |
| * Get @option as a #guint value. |
| * |
| * Returns: %TRUE if @value has been set or %FALSE if the option doesn't exist |
| * |
| * Since: 2.24 |
| */ |
| gboolean jsc_options_get_uint(const char* option, guint* value) |
| { |
| g_return_val_if_fail(option, FALSE); |
| g_return_val_if_fail(value, FALSE); |
| |
| GValue gValue = G_VALUE_INIT; |
| g_value_init(&gValue, G_TYPE_UINT); |
| if (!jscOptionsGetValue(option, &gValue)) |
| return FALSE; |
| |
| *value = g_value_get_uint(&gValue); |
| return TRUE; |
| } |
| |
| /** |
| * jsc_options_set_size: |
| * @option: the option identifier |
| * @value: the value to set |
| * |
| * Set @option as a #gsize value. |
| * |
| * Returns: %TRUE if option was correctly set or %FALSE otherwise. |
| * |
| * Since: 2.24 |
| */ |
| gboolean jsc_options_set_size(const char* option, gsize value) |
| { |
| g_return_val_if_fail(option, FALSE); |
| |
| GValue gValue = G_VALUE_INIT; |
| g_value_init(&gValue, G_TYPE_POINTER); |
| g_value_set_pointer(&gValue, GSIZE_TO_POINTER(value)); |
| return jscOptionsSetValue(option, &gValue); |
| } |
| |
| /** |
| * jsc_options_get_size: |
| * @option: the option identifier |
| * @value: (out): return location for the option value |
| * |
| * Get @option as a #gsize value. |
| * |
| * Returns: %TRUE if @value has been set or %FALSE if the option doesn't exist |
| * |
| * Since: 2.24 |
| */ |
| gboolean jsc_options_get_size(const char* option, gsize* value) |
| { |
| g_return_val_if_fail(option, FALSE); |
| g_return_val_if_fail(value, FALSE); |
| |
| GValue gValue = G_VALUE_INIT; |
| g_value_init(&gValue, G_TYPE_POINTER); |
| if (!jscOptionsGetValue(option, &gValue)) |
| return FALSE; |
| |
| *value = GPOINTER_TO_SIZE(g_value_get_pointer(&gValue)); |
| return TRUE; |
| } |
| |
| /** |
| * jsc_options_set_double: |
| * @option: the option identifier |
| * @value: the value to set |
| * |
| * Set @option as a #gdouble value. |
| * |
| * Returns: %TRUE if option was correctly set or %FALSE otherwise. |
| * |
| * Since: 2.24 |
| */ |
| gboolean jsc_options_set_double(const char* option, gdouble value) |
| { |
| g_return_val_if_fail(option, FALSE); |
| |
| GValue gValue = G_VALUE_INIT; |
| g_value_init(&gValue, G_TYPE_DOUBLE); |
| g_value_set_double(&gValue, value); |
| return jscOptionsSetValue(option, &gValue); |
| } |
| |
| /** |
| * jsc_options_get_double: |
| * @option: the option identifier |
| * @value: (out): return location for the option value |
| * |
| * Get @option as a #gdouble value. |
| * |
| * Returns: %TRUE if @value has been set or %FALSE if the option doesn't exist |
| * |
| * Since: 2.24 |
| */ |
| gboolean jsc_options_get_double(const char* option, gdouble* value) |
| { |
| g_return_val_if_fail(option, FALSE); |
| g_return_val_if_fail(value, FALSE); |
| |
| GValue gValue = G_VALUE_INIT; |
| g_value_init(&gValue, G_TYPE_DOUBLE); |
| if (!jscOptionsGetValue(option, &gValue)) |
| return FALSE; |
| |
| *value = g_value_get_double(&gValue); |
| return TRUE; |
| } |
| |
| /** |
| * jsc_options_set_string: |
| * @option: the option identifier |
| * @value: the value to set |
| * |
| * Set @option as a string. |
| * |
| * Returns: %TRUE if option was correctly set or %FALSE otherwise. |
| * |
| * Since: 2.24 |
| */ |
| gboolean jsc_options_set_string(const char* option, const char* value) |
| { |
| g_return_val_if_fail(option, FALSE); |
| |
| GValue gValue = G_VALUE_INIT; |
| g_value_init(&gValue, G_TYPE_STRING); |
| g_value_set_string(&gValue, value); |
| bool success = jscOptionsSetValue(option, &gValue); |
| g_value_unset(&gValue); |
| return success; |
| } |
| |
| /** |
| * jsc_options_get_string: |
| * @option: the option identifier |
| * @value: (out): return location for the option value |
| * |
| * Get @option as a string. |
| * |
| * Returns: %TRUE if @value has been set or %FALSE if the option doesn't exist |
| * |
| * Since: 2.24 |
| */ |
| gboolean jsc_options_get_string(const char* option, char** value) |
| { |
| g_return_val_if_fail(option, FALSE); |
| g_return_val_if_fail(value, FALSE); |
| |
| GValue gValue = G_VALUE_INIT; |
| g_value_init(&gValue, G_TYPE_STRING); |
| if (!jscOptionsGetValue(option, &gValue)) |
| return FALSE; |
| |
| *value = g_value_dup_string(&gValue); |
| g_value_unset(&gValue); |
| return TRUE; |
| } |
| |
| /** |
| * jsc_options_set_range_string: |
| * @option: the option identifier |
| * @value: the value to set |
| * |
| * Set @option as a range string. The string must be in the |
| * format <emphasis>[!]<low>[:<high>]</emphasis> where low and high are #guint values. |
| * Values between low and high (both included) will be considered in |
| * the range, unless <emphasis>!</emphasis> is used to invert the range. |
| * |
| * Returns: %TRUE if option was correctly set or %FALSE otherwise. |
| * |
| * Since: 2.24 |
| */ |
| gboolean jsc_options_set_range_string(const char* option, const char* value) |
| { |
| g_return_val_if_fail(option, FALSE); |
| |
| GValue gValue = G_VALUE_INIT; |
| g_value_init(&gValue, G_TYPE_STRING); |
| g_value_set_string(&gValue, value); |
| bool success = jscOptionsSetValue(option, &gValue); |
| g_value_unset(&gValue); |
| return success; |
| } |
| |
| /** |
| * jsc_options_get_range_string: |
| * @option: the option identifier |
| * @value: (out): return location for the option value |
| * |
| * Get @option as a range string. The string must be in the |
| * format <emphasis>[!]<low>[:<high>]</emphasis> where low and high are #guint values. |
| * Values between low and high (both included) will be considered in |
| * the range, unless <emphasis>!</emphasis> is used to invert the range. |
| * |
| * Returns: %TRUE if @value has been set or %FALSE if the option doesn't exist |
| * |
| * Since: 2.24 |
| */ |
| gboolean jsc_options_get_range_string(const char* option, char** value) |
| { |
| g_return_val_if_fail(option, FALSE); |
| g_return_val_if_fail(value, FALSE); |
| |
| GValue gValue = G_VALUE_INIT; |
| g_value_init(&gValue, G_TYPE_STRING); |
| if (!jscOptionsGetValue(option, &gValue)) |
| return FALSE; |
| |
| *value = g_value_dup_string(&gValue); |
| g_value_unset(&gValue); |
| return TRUE; |
| } |
| |
| static JSCOptionType jscOptionsType(bool) |
| { |
| return JSC_OPTION_BOOLEAN; |
| } |
| |
| static JSCOptionType jscOptionsType(int) |
| { |
| return JSC_OPTION_INT; |
| } |
| |
| #if CPU(ADDRESS64) |
| static JSCOptionType jscOptionsType(unsigned) |
| { |
| return JSC_OPTION_UINT; |
| } |
| #endif |
| |
| static JSCOptionType jscOptionsType(size_t) |
| { |
| return JSC_OPTION_SIZE; |
| } |
| |
| static JSCOptionType jscOptionsType(double) |
| { |
| return JSC_OPTION_DOUBLE; |
| } |
| |
| static JSCOptionType jscOptionsType(const char*) |
| { |
| return JSC_OPTION_STRING; |
| } |
| |
| static JSCOptionType jscOptionsType(const OptionRange&) |
| { |
| return JSC_OPTION_RANGE_STRING; |
| } |
| |
| static JSCOptionType jscOptionsType(const OSLogType&) |
| { |
| return JSC_OPTION_UINT; |
| } |
| |
| /** |
| * JSCOptionType: |
| * @JSC_OPTION_BOOLEAN: A #gboolean option type. |
| * @JSC_OPTION_INT: A #gint option type. |
| * @JSC_OPTION_UINT: A #guint option type. |
| * @JSC_OPTION_SIZE: A #gsize options type. |
| * @JSC_OPTION_DOUBLE: A #gdouble options type. |
| * @JSC_OPTION_STRING: A string option type. |
| * @JSC_OPTION_RANGE_STRING: A range string option type. |
| * |
| * Enum values for options types. |
| * |
| * Since: 2.24 |
| */ |
| |
| /** |
| * JSCOptionsFunc: |
| * @option: the option name |
| * @type: the option #JSCOptionType |
| * @description: (nullable): the option description, or %NULL |
| * @user_data: user data |
| * |
| * Function used to iterate options. |
| * |
| * Not that @description string is not localized. |
| * |
| * Returns: %TRUE to stop the iteration, or %FALSE otherwise |
| * |
| * Since: 2.24 |
| */ |
| |
| /** |
| * jsc_options_foreach: |
| * @function: (scope call): a #JSCOptionsFunc callback |
| * @user_data: callback user data |
| * |
| * Iterates all available options calling @function for each one. Iteration can |
| * stop early if @function returns %FALSE. |
| * |
| * Since: 2.24 |
| */ |
| void jsc_options_foreach(JSCOptionsFunc function, gpointer userData) |
| { |
| g_return_if_fail(function); |
| |
| #define VISIT_OPTION(type_, name_, defaultValue_, availability_, description_) \ |
| if (Options::Availability::availability_ == Options::Availability::Normal \ |
| || Options::isAvailable(Options::name_##ID, Options::Availability::availability_)) { \ |
| OptionsStorage::type_ defaultValue { }; \ |
| auto optionType = jscOptionsType(defaultValue); \ |
| if (function (#name_, optionType, description_, userData)) \ |
| return; \ |
| } |
| |
| Options::initialize(); |
| FOR_EACH_JSC_OPTION(VISIT_OPTION) |
| #undef VISIT_OPTION |
| } |
| |
| static gboolean setOptionEntry(const char* optionNameFull, const char* value, gpointer, GError** error) |
| { |
| const char* optionName = optionNameFull + 6; // Remove the --jsc- prefix. |
| GUniquePtr<char> option(g_strdup_printf("%s=%s", optionName, value)); |
| if (!Options::setOption(option.get())) { |
| g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Failed parse value '%s' for %s", value, optionNameFull); |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| /** |
| * jsc_options_get_option_group: |
| * |
| * Create a #GOptionGroup to handle JSCOptions as command line arguments. |
| * The options will be exposed as command line arguments with the form |
| * <emphasis>--jsc-<option>=<value></emphasis>. |
| * Each entry in the returned #GOptionGroup is configured to apply the |
| * corresponding option during command line parsing. Applications only need to |
| * pass the returned group to g_option_context_add_group(), and the rest will |
| * be taken care for automatically. |
| * |
| * Returns: (transfer full): a #GOptionGroup for the JSCOptions |
| * |
| * Since: 2.24 |
| */ |
| GOptionGroup* jsc_options_get_option_group(void) |
| { |
| // GOptionEntry works with const strings, so we need to keep the option names around. |
| auto* names = new Vector<GUniquePtr<char>>; |
| GOptionGroup* group = g_option_group_new("jsc", _("JSC Options"), _("Show JSC Options"), names, [] (gpointer data) { |
| delete static_cast<Vector<GUniquePtr<char>>*>(data); |
| }); |
| g_option_group_set_translation_domain(group, GETTEXT_PACKAGE); |
| |
| GArray* entries = g_array_new(TRUE, TRUE, sizeof(GOptionEntry)); |
| #define REGISTER_OPTION(type_, name_, defaultValue_, availability_, description_) \ |
| if (Options::Availability::availability_ == Options::Availability::Normal \ |
| || Options::isAvailable(Options::name_##ID, Options::Availability::availability_)) { \ |
| GUniquePtr<char> name(g_strdup_printf("jsc-%s", #name_)); \ |
| entries = g_array_set_size(entries, entries->len + 1); \ |
| GOptionEntry* entry = &g_array_index(entries, GOptionEntry, entries->len - 1); \ |
| entry->long_name = name.get(); \ |
| entry->arg = G_OPTION_ARG_CALLBACK; \ |
| entry->arg_data = reinterpret_cast<gpointer>(setOptionEntry); \ |
| entry->description = description_; \ |
| names->append(WTFMove(name)); \ |
| } |
| |
| Options::initialize(); |
| FOR_EACH_JSC_OPTION(REGISTER_OPTION) |
| #undef REGISTER_OPTION |
| |
| g_option_group_add_entries(group, reinterpret_cast<GOptionEntry*>(entries->data)); |
| return group; |
| } |
| |
| /** |
| * JSC_OPTIONS_USE_JIT: |
| * |
| * Allows the executable pages to be allocated for JIT and thunks if %TRUE. |
| * Option type: %JSC_OPTION_BOOLEAN |
| * Default value: %TRUE. |
| * |
| * Since: 2.24 |
| */ |
| |
| /** |
| * JSC_OPTIONS_USE_DFG: |
| * |
| * Allows the DFG JIT to be used if %TRUE. |
| * Option type: %JSC_OPTION_BOOLEAN |
| * Default value: %TRUE. |
| * |
| * Since: 2.24 |
| */ |
| |
| /** |
| * JSC_OPTIONS_USE_FTL: |
| * |
| * Allows the FTL JIT to be used if %TRUE. |
| * Option type: %JSC_OPTION_BOOLEAN |
| * Default value: %TRUE. |
| * |
| * Since: 2.24 |
| */ |
| |
| /** |
| * JSC_OPTIONS_USE_LLINT: |
| * |
| * Allows the LLINT to be used if %TRUE. |
| * Option type: %JSC_OPTION_BOOLEAN |
| * Default value: %TRUE. |
| * |
| * Since: 2.24 |
| */ |