Web - Simple example

WebKit-EFL is independent of any particular toolkit, such as Elementary, so using it on applications requires that the programmer writes a lot of boiler plate code to manage to manage the web object.

For a full featured browser this may make sense, as the programmer will want to have full control of every aspect of the web object, since it's the main component of the application. But other programs with simpler requirements, having to write so much code is undesired.

This is where elm_web comes in. Its purpose is to provide a simple way for developers to embed a simple web object in their programs, simplifying the common use cases.

This is not to say that a browser can't be made out of it, as this example shows.

We'll be making a simple browser, consisting of one window with an URL bar, a toolbar to be used for the tabs and a pager to show one page at a time.

When all tabs are closed, we'll be showing a default view with some custom content, for which we need to get the internal ewk_view object and use some WebKit functions on it, thus we need to include the necessary headers first.

#include <Elementary.h>
#ifdef HAVE_ELEMENTARY_WEB
#include <EWebKit.h>

A struct to keep track of the different widgets in use and the currently shown tab. There's also an exiting flag, used to work around the overly simplistic way in which this example is written, just to avoid some warnings when closing the program.

typedef struct _Tab_Data Tab_Data;
typedef struct
{
Evas_Object *win;
Evas_Object *main_box;
Evas_Object *naviframe;
Evas_Object *url_entry;
Evas_Object *default_web;
Evas_Object *tabs;
Evas_Object *close_tab;
Evas_Object *search_box;
Evas_Object *search_entry;
struct {
Evas_Object *back;
Evas_Object *fwd;
Evas_Object *refresh;
} nav;
Tab_Data *current_tab;
Eina_Bool exiting : 1;
} App_Data;

Each tab has its own struct too, but there's not much to it.

struct _Tab_Data
{
Evas_Object *web;
App_Data *app;
};

Whenever the currently selected tab changes, we need to update some state on the application. The back and forward buttons need to be disabled accordingly and the URL bar needs to show the right address.

static void
nav_button_update(App_Data *ad)
{
Eina_Bool back, fwd;
back = !elm_web_back_possible_get(ad->current_tab->web);
fwd = !elm_web_forward_possible_get(ad->current_tab->web);
elm_object_disabled_set(ad->nav.back, back);
elm_object_disabled_set(ad->nav.fwd, fwd);
}
static void
tab_current_set(Tab_Data *td)
{
const char *url;
if (td == td->app->current_tab)
return;
td->app->current_tab = td;
url = elm_web_url_get(td->web);
elm_object_text_set(td->app->url_entry, url);
nav_button_update(td->app);
elm_entry_icon_visible_set(td->app->url_entry, EINA_TRUE);
elm_naviframe_item_simple_promote(td->app->naviframe, td->web);
}

Other updates happen based on events from the web object, like title change to update the name shown in the tab, and URL change which will update the URL bar if the event came from the currently selected tab.

static void
_title_changed_cb(void *data, Evas_Object *obj, void *event_info)
{
Tab_Data *td = data;
const char *title = event_info;
char buf[20] = "";
if (title)
strncpy(buf, title, sizeof(buf) - 1);
elm_object_item_text_set(td->tab, buf);
}
static void
_url_changed_cb(void *data, Evas_Object *obj, void *event_info)
{
Tab_Data *td = data;
const char *url = event_info;
if (td != td->app->current_tab)
return;
nav_button_update(td->app);
elm_object_text_set(td->app->url_entry, url);
}

Adding a new tab is just a matter of creating a new web widget, its data and pushing it into the pager. A lot of the things that we should handle here, such as how to react to popups and JavaScript dialogs, are done already in the elm_web widget, so we can rely on their default implementations. For the JavaScript dialogs we are going to avoid having them open in a new window by setting the Inwin mode.

There is no default implementation, however, for the requests to create a new window, so we have to handle them by setting a callback function that will ultimately call this very same function to add a new tab.

Tab_Data *
tab_add(App_Data *ad)
{
Tab_Data *td;
td = calloc(1, sizeof(Tab_Data));
if (!td) return NULL;
td->web = elm_web_add(ad->win);
elm_web_window_create_hook_set(td->web, _web_create_window_cb, ad);
elm_web_inwin_mode_set(td->web, EINA_TRUE);
evas_object_size_hint_weight_set(td->web, EVAS_HINT_EXPAND,
EVAS_HINT_EXPAND);
evas_object_size_hint_align_set(td->web, EVAS_HINT_FILL, EVAS_HINT_FILL);
elm_naviframe_item_simple_push(ad->naviframe, td->web);
td->app = ad;
td->tab = elm_toolbar_item_append(td->app->tabs, NULL, "New tab",
_tab_clicked_cb, td);
elm_object_item_del_cb_set(td->tab, _tb_item_del_cb);
evas_object_data_set(td->web, "tab_data", td);
evas_object_smart_callback_add(td->web, "title,changed", _title_changed_cb,
td);
evas_object_smart_callback_add(td->web, "url,changed", _url_changed_cb, td);
evas_object_event_callback_add(td->web, EVAS_CALLBACK_FREE, _web_free_cb,
td);
elm_toolbar_item_selected_set(td->tab, EINA_TRUE);
return td;
}

Entering an address in the URL bar will check if a tab exists, and if not, create one and set the URL for it. The address needs to conform to the URI format, so we check that it does and add the protocol if it's missing.

static char *
url_sanitize(const char *url)
{
char *fixed_url;
char *schema;
char *tmp;
if (!url || !*url) return NULL;
tmp = strstr(url, "://");
if (!tmp || (tmp == url) || (tmp > (url + 15)))
{
char *new_url = NULL;
if (ecore_file_exists(url))
{
schema = "file";
new_url = ecore_file_realpath(url);
}
else
schema = "http";
if (asprintf(&fixed_url, "%s://%s", schema, new_url ? new_url : url) >
0)
{
free(new_url);
return fixed_url;
}
free(new_url);
}
else
return strdup(url);
return NULL;
}
static void
tab_url_set(Tab_Data *td, const char *url)
{
char *sane_url = url_sanitize(url);
elm_web_url_set(td->web, sane_url);
free(sane_url);
}
static void
_url_entry_activated_cb(void *data, Evas_Object *obj, void *event_info)
{
App_Data *ad = data;
Tab_Data *td;
const char *url = eina_stringshare_ref(elm_object_text_get(obj));
if (!ad->current_tab)
td = tab_add(ad);
else
td = ad->current_tab;
tab_url_set(td, url);
eina_stringshare_del(url);
}

The navigation buttons are simple enough. As for the refresh, it normally reloads the page using anything that may exist in the caches if applicable, but we can press it while holding the Shift key to avoid the cache.

static void
_nav_back_cb(void *data, Evas_Object *obj, void *event_info)
{
App_Data *ad = data;
elm_web_back(ad->current_tab->web);
}
static void
_nav_refresh_cb(void *data, Evas_Object *obj, void *event_info)
{
App_Data *ad = data;
const Evas_Modifier *mods = evas_key_modifier_get(evas_object_evas_get(obj));
if (evas_key_modifier_is_set(mods, "Shift"))
elm_web_reload_full(ad->current_tab->web);
else
elm_web_reload(ad->current_tab->web);
}
static void
_nav_fwd_cb(void *data, Evas_Object *obj, void *event_info)
{
App_Data *ad = data;
elm_web_forward(ad->current_tab->web);
}

The callback set for the new window request creates a new tab and returns the web widget associated with it. This is important, this function must return a valid web widget returned by elm_web_add().

static Evas_Object *
_web_create_window_cb(void *data, Evas_Object *obj, Eina_Bool js, const Elm_Web_Window_Features *wf)
{
App_Data *ad = data;
Tab_Data *td;
td = tab_add(ad);
return td->web;
}

Pressing Ctrl-F will bring up the search box. Nothing about the box itself is worth mentioning here, but it works as you would expect from any other browser. While typing on it, it will highlight all occurrences of the searched word. Pressing Enter will go to the next instance and the two buttons next to the entry will move forward and backwards through the found keywords.

static void
_win_free_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
free(data);
}
static void
_search_entry_changed_cb(void *data, Evas_Object *obj, void *event_info)
{
App_Data *ad = data;
const char *text;
text = elm_object_text_get(obj);
elm_web_text_search(ad->current_tab->web, text, EINA_FALSE, EINA_TRUE,
EINA_TRUE);
elm_web_text_matches_unmark_all(ad->current_tab->web);
elm_web_text_matches_mark(ad->current_tab->web, text, EINA_FALSE, EINA_TRUE,
0);
}
static void
_search_entry_activate_cb(void *data, Evas_Object *obj, void *event_info)
{
App_Data *ad = data;
const char *text;
text = elm_object_text_get(obj);
elm_web_text_search(ad->current_tab->web, text, EINA_FALSE, EINA_TRUE,
EINA_TRUE);
}
static void
_search_next_cb(void *data, Evas_Object *obj, void *event_info)
{
App_Data *ad = data;
const char *text;
text = elm_object_text_get(ad->search_entry);
elm_web_text_search(ad->current_tab->web, text, EINA_FALSE, EINA_TRUE,
EINA_TRUE);
}
static void
_search_prev_cb(void *data, Evas_Object *obj, void *event_info)
{
App_Data *ad = data;
const char *text;
text = elm_object_text_get(ad->search_entry);
elm_web_text_search(ad->current_tab->web, text, EINA_FALSE, EINA_FALSE,
EINA_TRUE);
}
static void
_search_close_cb(void *data, Evas_Object *obj, void *event_info)
{
App_Data *ad = data;
evas_object_del(ad->search_box);
}
static void
_search_box_del_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
App_Data *ad = data;
ad->search_box = NULL;
ad->search_entry = NULL;
}
static void
_win_search_trigger_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
Evas_Event_Key_Down *ev = event_info;
App_Data *ad = data;
Evas_Object *box, *box2, *entry, *btn, *ic;
if (strcmp(ev->keyname, "f") ||
!evas_key_modifier_is_set(ev->modifiers, "Control"))
return;
if (ad->search_box || !ad->current_tab)
return;
box = elm_box_add(ad->win);
elm_box_horizontal_set(box, EINA_TRUE);
evas_object_size_hint_weight_set(box, EVAS_HINT_EXPAND, 0.0);
evas_object_size_hint_align_set(box, EVAS_HINT_FILL, EVAS_HINT_FILL);
elm_box_pack_after(ad->main_box, box, ad->url_entry);
evas_object_show(box);
evas_object_event_callback_add(box, EVAS_CALLBACK_DEL, _search_box_del_cb,
ad);
entry = elm_entry_add(ad->win);
elm_entry_single_line_set(entry, EINA_TRUE);
elm_entry_scrollable_set(entry, EINA_TRUE);
evas_object_size_hint_weight_set(entry, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
evas_object_size_hint_align_set(entry, EVAS_HINT_FILL, EVAS_HINT_FILL);
elm_box_pack_end(box, entry);
evas_object_show(entry);
evas_object_smart_callback_add(entry, "changed", _search_entry_changed_cb,
ad);
evas_object_smart_callback_add(entry, "activated", _search_entry_activate_cb,
ad);
box2 = elm_box_add(ad->win);
elm_box_horizontal_set(box2, EINA_TRUE);
elm_object_part_content_set(entry, "end", box2);
btn = elm_button_add(ad->win);
elm_box_pack_end(box2, btn);
evas_object_show(btn);
ic = elm_icon_add(ad->win);
elm_icon_standard_set(ic, "arrow_up");
elm_object_part_content_set(btn, "icon", ic);
evas_object_smart_callback_add(btn, "clicked", _search_prev_cb, ad);
btn = elm_button_add(ad->win);
elm_box_pack_end(box2, btn);
evas_object_show(btn);
ic = elm_icon_add(ad->win);
elm_icon_standard_set(ic, "arrow_down");
elm_object_part_content_set(btn, "icon", ic);
evas_object_smart_callback_add(btn, "clicked", _search_next_cb, ad);
btn = elm_button_add(ad->win);
elm_box_pack_end(box, btn);
evas_object_show(btn);
ic = elm_icon_add(ad->win);
elm_icon_standard_set(ic, "close");
elm_object_part_content_set(btn, "icon", ic);
evas_object_smart_callback_add(btn, "clicked", _search_close_cb, ad);
ad->search_box = box;
ad->search_entry = entry;
elm_object_focus_set(entry, EINA_TRUE);
}

Last, create the main window and put all of the things used above in it. It contains a default web widget that will be shown when no tabs exist. This web object is not browsable per se, so history is disabled in it, and we set the same callback to create new windows, on top of setting some custom content of our own on it, with some links that will open new tabs to start browsing quickly.

static void
default_content_set(Evas_Object *web)
{
#ifdef HAVE_ELEMENTARY_WEB
Evas_Object *view, *frame;
const char contents[] = ""
"<html>\n"
" <head>\n"
" <title>Nothing to see here, move along</title>\n"
" </head>\n"
" <body>\n"
" <a href=\"http://www.enlightenment.org\" target=\"_blank\">E</a>\n"
" <br />\n"
" <a href=\"http://www.google.com\" target=\"_blank\">Google</a>\n"
" <br />\n"
" </body>\n"
"</html>\n";
frame = ewk_view_frame_main_get(view);
ewk_frame_contents_set(frame, contents, sizeof(contents) - 1, "text/html",
"UTF-8", NULL);
#else
(void) web;
#endif
}
EAPI_MAIN int
elm_main(int argc, char *argv[])
{
Evas_Object *win, *box, *box2, *btn, *ic, *url_bar, *naviframe, *tabs, *web;
Evas *e;
Evas_Modifier_Mask ctrl_mask;
App_Data *ad;
if (!elm_need_web())
return -1;
ad = calloc(1, sizeof(App_Data));
if (!ad) return -1;
elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED);
win = elm_win_util_standard_add("example-web", "Web Example");
elm_win_autodel_set(win, EINA_TRUE);
e = evas_object_evas_get(win);
ctrl_mask = evas_key_modifier_mask_get(e, "Control");
if (!evas_object_key_grab(win, "f", ctrl_mask, 0, EINA_TRUE))
fprintf(stderr, "Could not grab trigger for search dialog\n");
evas_object_smart_callback_add(win, "delete,request", _win_del_request_cb,
ad);
evas_object_event_callback_add(win, EVAS_CALLBACK_KEY_DOWN,
_win_search_trigger_cb, ad);
evas_object_event_callback_add(win, EVAS_CALLBACK_FREE, _win_free_cb, ad);
box = elm_box_add(win);
evas_object_size_hint_weight_set(box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
evas_object_show(box);
url_bar = elm_entry_add(win);
elm_entry_single_line_set(url_bar, EINA_TRUE);
elm_entry_scrollable_set(url_bar, EINA_TRUE);
evas_object_size_hint_weight_set(url_bar, EVAS_HINT_EXPAND, 0.0);
evas_object_size_hint_align_set(url_bar, EVAS_HINT_FILL, EVAS_HINT_FILL);
elm_box_pack_end(box, url_bar);
evas_object_show(url_bar);
evas_object_smart_callback_add(url_bar, "activated", _url_entry_activated_cb, ad);
box2 = elm_box_add(win);
elm_box_horizontal_set(box2, EINA_TRUE);
elm_object_part_content_set(url_bar, "icon", box2);
elm_entry_icon_visible_set(url_bar, EINA_FALSE);
btn = elm_button_add(win);
elm_box_pack_end(box2, btn);
evas_object_show(btn);
ad->nav.back = btn;
ic = elm_icon_add(win);
elm_icon_standard_set(ic, "arrow_left");
elm_object_part_content_set(btn, "icon", ic);
evas_object_smart_callback_add(btn, "clicked", _nav_back_cb, ad);
btn = elm_button_add(win);
elm_box_pack_end(box2, btn);
evas_object_show(btn);
ad->nav.refresh = btn;
ic = elm_icon_add(win);
elm_icon_standard_set(ic, "refresh");
elm_object_part_content_set(btn, "icon", ic);
evas_object_smart_callback_add(btn, "clicked", _nav_refresh_cb, ad);
btn = elm_button_add(win);
elm_box_pack_end(box2, btn);
evas_object_show(btn);
ad->nav.fwd = btn;
ic = elm_icon_add(win);
elm_icon_standard_set(ic, "arrow_right");
elm_object_part_content_set(btn, "icon", ic);
evas_object_smart_callback_add(btn, "clicked", _nav_fwd_cb, ad);
box2 = elm_box_add(win);
elm_box_horizontal_set(box2, EINA_TRUE);
evas_object_size_hint_weight_set(box2, EVAS_HINT_EXPAND, 0.0);
evas_object_size_hint_align_set(box2, EVAS_HINT_FILL, EVAS_HINT_FILL);
elm_box_pack_end(box, box2);
evas_object_show(box2);
btn = elm_button_add(win);
elm_box_pack_end(box2, btn);
evas_object_show(btn);
ic = elm_icon_add(win);
elm_icon_standard_set(ic, "file");
elm_object_part_content_set(btn, "icon", ic);
evas_object_smart_callback_add(btn, "clicked", _add_tab_cb, ad);
tabs = elm_toolbar_add(win);
elm_toolbar_select_mode_set(tabs, ELM_OBJECT_SELECT_MODE_ALWAYS);
elm_toolbar_homogeneous_set(tabs, EINA_FALSE);
elm_toolbar_shrink_mode_set(tabs, ELM_TOOLBAR_SHRINK_MENU);
evas_object_size_hint_weight_set(tabs, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
evas_object_size_hint_align_set(tabs, EVAS_HINT_FILL, EVAS_HINT_FILL);
elm_box_pack_end(box2, tabs);
evas_object_show(tabs);
btn = elm_button_add(win);
elm_box_pack_end(box2, btn);
evas_object_show(btn);
evas_object_smart_callback_add(btn, "clicked", _close_tab_cb, ad);
ic = elm_icon_add(win);
elm_icon_standard_set(ic, "close");
elm_object_part_content_set(btn, "icon", ic);
naviframe = elm_naviframe_add(win);
evas_object_size_hint_weight_set(naviframe, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
evas_object_size_hint_align_set(naviframe, EVAS_HINT_FILL, EVAS_HINT_FILL);
elm_box_pack_end(box, naviframe);
evas_object_show(naviframe);
elm_toolbar_menu_parent_set(tabs, naviframe);
web = elm_web_add(win);
elm_web_window_create_hook_set(web, _web_create_window_cb, ad);
elm_web_history_enabled_set(web, EINA_FALSE);
evas_object_size_hint_weight_set(web, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
evas_object_size_hint_align_set(web, EVAS_HINT_FILL, EVAS_HINT_FILL);
elm_naviframe_item_simple_push(naviframe, web);
default_content_set(web);
ad->win = win;
ad->main_box = box;
ad->naviframe = naviframe;
ad->url_entry = url_bar;
ad->default_web = web;
ad->tabs = tabs;
ad->close_tab = btn;
evas_object_resize(win, 480, 640);
evas_object_show(win);
return 0;
}

Some parts of the code were left out, as they are not relevant to the example, but the full listing can be found at web_example_02.c.