# Writing Epiphany Extensions

Adam
Hooper

    					adamh@densi.com

0.07
29-Jan-2005
ah
Inaugurate Pyphany.
0.06
30-Oct-2004
ah
Put titles on examples, and ensure all examples work with unloading.
0.05
30-Oct-2004
ah
Replaced ephyplugins with epiphany-extensions. Got rid of now-incorrect statements. Mention copy-template script, which saves the user from copy/pasting everything.
0.04
14-Jan-2004
ah
Replaced Savannah links to point to the page on Epiphany's website.
0.03
13-Jan-2004
ah
s/Firdbird/Firebird/
0.02
03-Jan-2004
ah
I've implemented all changes suggested by lwillis and chpe and done some clarifications of my own.
0.01
01-Jan-2004
ah
My first shot at a great HOWTO. Surelly loaded with typos and minor errors.

## Introduction

### What Epiphany Extensions Are

Epiphany extensions are similar to Mozilla Firefox extensions: they are entirely optional and change the way the browser works (usually by expanding its feature set). For example, an Epiphany extension could provide mouse gestures, track your browsing behavior, or even replace the entire Bookmarks system (though such an extension would be rather complicated).

Epiphany extensions are different from Mozilla Firefox's because they are implemented in C or Python as opposed to Javascript. This gives enormous flexibility and a bit of added challenge, but it should be no problem for even a beginner to develop an extension which crashes Epiphany on startup.

Epiphany extensions are at a lower level code-wise than Mozilla Firefox's; there is no simple way to convert a Mozilla Firefox extension to an Epiphany one. This should be repeated: Epiphany extensions are not interchangeable with Firefox extensions.

### Document Scope

This document is not a C or Python programming tutorial. It is also not a guide to the Epiphany source code. It is just a basic guide to begin writing an extension.

The document is written under the assumption that the reader wants to write an extension. The document will be an aid, but it does not replace reading actual source code from existing extensions. Many extensions are available at .

### Getting Help

For more help writing Epiphany Extensions, log on to `#epiphany` on `irc.gnome.org`. You can also use the regular Epiphany Mailing List.

As with all software projects' channels of communication, please do not ask stupid questions. If you've never read How to Ask Questions the Smart Way by Eric S. Raymond, read it now. It's a big pile of common sense, and it applies to virtually every open-source software project in existence.

If you are a proficient hacker and don't understand some part of this document, that's a bug in the document; please email the author.

### About the Author

I am Adam Hooper, age 21, second-year Software Engineering student at McGill University in Montreal, Quebec, Canada. I can share my experiences with several extensions, including a popup blocker, HTML error viewer and page info dialog.

My email address is adamh@densi.com; feel free to contact me about this document (or most other things, really).

## Preparation

### Required Experience

Knowledge of C or Python is required. Familiarity with GTK+ and Glib (especially GObjects and signals) can also be quite important. Familiarity with the Epiphany source code would be fantastic, but if you don't have it… you will before you're finished writing your extension!

### Required Programs

Obviously, you will need Epiphany installed, and all of its required libraries. Development headers must be installed, too (for most distributions, you must install both the regular library and the `-dev` or `-devel` packages— but if you don't know this already, just give up now and save yourself the confusion). You will typically need GTK, GLib and Epiphany development headers as a minimum; depending on what your extension does, this list may be much larger.

This document suggests features only found in Epiphany CVS; if a particular feature does not work for you, it could be that your version of Epiphany is too old.

It is highly recommended that you install epiphany-extensions as well. Besides providing a user interface to enable and disable extensions (so you don't have to edit the GConf keys manually), it also provides a C build framework.

To compile your extension with the existing C build framework, you will need GNU autoconf, GNU automake, libtool, gettext, etc. Basically, you must be able to compile Epiphany from CVS. In fact, you might as well install Epiphany from CVS before coding.

If you want to write your extension using Python, you should still install epiphany-extensions for its extension enabling user interface. You must also install pyphany, which includes Python bindings, a Python script loader, and some sample extensions.

### Required Source Code

You will need the source code to Epiphany. It is available at . It's a good idea to code an extension for the newest version of Epiphany available; from CVS is best.

The existing GNOME Web Browser Extensions source code has many working (and non-working) examples.

### Required Documentation

GTK+ and Glib documentation are available at , but a locally-installed set of documentation is preferable. If you installed GTK+ from CVS or a recent tarball (at the time this document was written, doing so is necessary for Epiphany CVS to compile), be sure to install the gtk-doc documentation along with it.

The author uses Devhelp for quick access to Glib and GTK+ documentation. There is some Epiphany documentation available, but it is not complete.

The GNOME Human Interface Guidelines: this documentation is not strictly required, but it should be treated with at least as much respect as exists towards the Bible, Koran, Torah and all other holy books.

### Before Coding

As with any software project, be sure to plan what your extension should do before writing it. At the very least, you should have a good idea of what events (GLib signals) will trigger your extension, and what it will do.

Read Epiphany's `HACKING` file. Adhere to those standards.

### Required License

Epiphany extensions are classified as derivative works of Epiphany, which means they must be licensed under the GNU GPL.

## Build Framework

If you are coding your extension in C, you will have to build it at some point; this chapter will detail a simple way to do so. If you are coding your extension in Python, you can skip this chapter entirely.

To avoid autoconf/automake hell, you could simply code your extension in the existing epiphany-extensions framework (download the source from ). To do so:

1. Run ``epiphany-extensions/tools/copy-template` foo`. This will copy the sample template into a new subdirectory in `epiphany-extensions/extensions/`.

2. Edit `epiphany-extensions/extensions/Makefile.am` and add your SUBDIRS entry:

       if ENABLE_FOO_EXTENSION
       SUBDIRS += foo
       endif

   Also, add your extension to the end of the `DIST_SUBDIRS` line.

3. Edit `epiphany-extensions/configure.ac` to add your own extension:

   - Add a line near the similar existing ones:

         AM_CONDITIONAL(ENABLE_FOO_EXTENSION, echo "$extensions" | egrep '(^|,)foo($|,)' > /dev/null)

   - Add `extensions/foo/Makefile` to the `AC_OUTPUT` call.

   - Add your extension to the `all_extensions=` line.

4. If your extension will include strings which should be translated, edit `epiphany-extensions/po/POTFILES.in` and add `extensions/foo/ephy-foo-extension.c`. (No, the filename is not important.)

5. Edit `epiphany-extensions/extensions/foo/foo.xml.in.in` to describe your extension.

Now all work will take place in the `epiphany-extensions/extensions/foo/` directory. After coding is finished, run `./autogen.sh --enable-debug --enable-extensions=foo` in the `epiphany-extensions/` directory, then `make` and `make install` as root.

## Coding

In any conflict between Epiphany's C coding style and this document's, Epiphany's is right and this document's is wrong. Particularly, tabs are 8 spaces wide and are tabs, and in each function declaration, each argument should appear on its own line, in line with the argument above it.

There is no coding standard for Python extensions.

The C `LOG` macro is included in `ephy-debug.h`. If you use `--enable-debug` while running `configure` or `autogen.sh` from within the `epiphany-extensions/` directory, the messages will be printed with `g_print`, which uses a `printf`-like syntax.

### `register_module`

This function is only necessary for C extensions; Python coders may skip this section.

Epiphany will look for a `register_module` function when loading the extension. This function is defined in `extension.c` if you used the `copy-template` script.

Read over the default example to understand what is expected: `register_module` must return a GObject type. Almost always, this GObject should implement the EphyExtension interface. In the template extension, this is described in the last line of the function:

    return ephy_foo_extension_register_type (module);

. If you wish to register other GObject types, simply put their register_type calls above this one.

Extensions may be loaded or unloaded at any time. This means they must be registered using `g_type_module_register_type`. Also, due to a limitation in that function, if you update your extension to change the parent class of a particular GObject, unload the extension and then reload it within Epiphany, the new GObject class will fail to register. To avoid problems, when changing the parent class of a GObject in your extension, increment the `version` in the extension's XML description file.

### Header File

The `extension.c` file above referred to `ephy-foo-extension.h`, so that header file must be present. An extension is a `GObject`. There are no surprises in the header file: it's a standard header for a GObject. It's quite possible that editing this file (after creating it with `copy-template`) is unnecessary.

### Minimum C Code

As a GObject, the extension requires some actual code. The `copy-template` script will generate the basic structure for `ephy-foo-extension.c`, but you must fill in the details to make this extension actually do something. What to fill in is the focus of the rest of this document.

Before you continue, feel free to remove all the property setting/getting code if it's unneeded.

If your GObject implements the `EphyExtension` interface (and it should), it may implement the following functions. These are the hooks through which the extension can attach signals to accomplish pretty much anything.

attach_window

  Called when a new window is created. Passed the `EphyExtension` and `EphyWindow`.

detach_window

  Called when a window is being finalized. Passed the `EphyExtension` and `EphyWindow`.

attach_tab

  Called when a new tab is created. Passed the `EphyExtension`, the `EphyWindow` and the `EphyTab`.

detach_tab

  Called before a tab is finalized. Passed the `EphyExtension`, the `EphyWindow` and the `EphyTab`.

The template file will contain empty `impl_attach_window` and `impl_detach_window` when it is first created.

Python extensions may use these same functions. When using Python, the `EphyExtension` argument is never passed (just store data structures as globals in the Python module).

### Connecting Signals

GLib has a very flexible signals system. Use it. The `attach_window` function has an `EphyWindow` argument. As an example, the `switch_page` signal will be fired on a window's tab whenever the tab changes. Put the following in `attach_window` to hook the `switch_page` signal to your own callback:

#### Connecting the switch_page signal

    def _switch_page_cb(notebook, page, page_num, data):
    	print data

    def attach_window(window):
    	notebook = window.get_notebook()
    	sig = notebook.connect('switch_page', _switch_page_cb, 'Foo')
    	notebook._example_extension_sig = sig

    def detach_window(window):
    	notebook = window.get_notebook()
    	sig = notebook._example_extension_sig
    	notebook.disconnect(sig)
    	del notebook._example_extension_sig

`tab_added` can connect signals of its own. Of particular interest are the `EphyEmbed` signals (prefixed by `ge_`). These signals are listed in `epiphany/embed/ephy-embed.c`. For example, to call a function when the browser changes to a new location:

#### Listening to location changes

    def _location_cb(embed, address):
    	print 'New location: %s' % address

    def attach_tab(window, tab):
    	embed = tab.get_embed()
    	sig = embed.connect('ge-location', _location_cb)
    	embed._python_sample_location_sig = sig

    def detach_tab(window, tab):
    	embed = tab.get_embed()
    	sig = embed._python_sample_location_sig
    	del embed._python_sample_location_sig
    	embed.disconnect(sig)

Free all memory and disconnect all signals when they're not needed. In the case of this `ge_location` signal connection, disconnect the signal when the tab is removed.

Python's garbage collector frees memory properly, but it does not disconnect signals; you must disconnect signals.

### Translation

If you need to translate some strings (if your extension prints or displays any messages besides debug messages), a few steps must be followed:

- For every string needing translation, use the `N_`, `_` or `ngettext` functions. For example:

  #### Basic gettext usage

      g_print (_("This is a foobar. It says %s."), message);
      g_print (ngettext ("%d cow jumped over the moon", "%d cows jumped over the moon", n), n);

- Add the filenames with translatable strings to `epiphany-extensions/po/POTFILES.in`.

### User Interface Integration

This section describes changing Epiphany's user interface. Epiphany developers take pride in Epiphany's simplicity and ease of use: you should continue the trend. UI additions should be simple, minor, and non-intrusive. Carefully plan each addition.

The `GtkUIManager` functions allow you to add new user interface elements. Epiphany's `ephy_window_get_ui_manager` will return the appropriate `GtkUIManager` for an `EphyWindow`. Use `GtkAction`s to represent actions. For example, an extension could place an action at the bottom of the Go menu of Epiphany. To do so, write the following in `ephy-foo-extension.c`:

#### Adding menu entries

    #include <gtk/gtkaction.>
    #include <gtk/gtkactiongroup>
    #include <gtk/gtkuimanager.h>

    #include <glib/gi18n-lib.h> /* see  */

    #define WINDOW_DATA_KEY "EphyFooExtensionWindowData"

    typedef struct
    {
    	GtkActionGroup *action_group;
    	guint ui_id;
    } WindowData;

    static void my_action_activate_cb (GtkAction *action, EphyWindow *window);

    /* ... */

    static GtkActionEntry action_entries [] =
    {
    	{ "MyAction",
    	  NULL,
    	  N_("Menu Entry Name"),
    	  NULL, /* shortcut key */
    	  N_("Does Whatever I Want"),
    	  G_CALLBACK (my_action_activate_cb) }
    };
    static const guint n_action_entries = G_N_ELEMENTS (action_entries);

    static void my_action_activate_cb (GtkAction *action,
    				   EphyWindow *window)
    {
    	/* Do something */
    }

    /* ... */

    static void
    free_window_data (WindowData *data)
    {
    	g_object_unref (data->action_group);
    	g_free (data);
    }

    static void
    impl_attach_window (EphyExtension *extension,
    		    EphyWindow *window)
    {
    	GtkActionGroup *action_group;
    	GtkUIManager *manager;
    	guint merge_id;
    	WindowData *data;

    	data = g_new (WindowData, 1);

    	manager = GTK_UI_MANAGER (ephy_window_get_ui_manager (window));

    	data->action_group = action_group =
    		gtk_action_group_new ("EphyFooExtensionActions");

    	gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
    	gtk_action_group_add_actions (action_group, action_entries,
    				      n_action_entries, window);

    	gtk_ui_manager_insert_action_group (manager, action_group, 0);

    	data->ui_id = merge_id = gtk_ui_manager_new_merge_id (manager);

    	g_data_set_full (G_OBJECT (window), WINDOW_DATA_KEY, data,
    			 (GDestroyNotify) free_window_data);

    	gtk_ui_manager_add_ui (manager, merge_id, "/menubar/GoMenu",
    			       "MyActionSep1", NULL,
    			       GTK_UI_MANAGER_SEPARATOR, FALSE);
    	gtk_ui_manager_add_ui (manager, merge_id, "/menubar/GoMenu",
    			       "MyAction", "MyAction",
    			       GTK_UI_MANAGER_MENUITEM, FALSE);
    	gtk_ui_manager_add_ui (manager, merge_id, "/menubar/GoMenu",
    			       "MyActionSep2", NULL,
    			       GTK_UI_MANAGER_SEPARATOR, FALSE);
    }

    static void
    impl_detach_window (EphyExtension *extension,
    		    EphyWindow *window)
    {
    	GtkUIManager *manager;
    	WindowData *data;

    	manager = GTK_UI_MANAGER (ephy_window_get_ui_manager (window));

    	data = (WindowData *) g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY);
    	g_return_if_fail (data != NULL);

    	gtk_ui_manager_remove_ui (manager, data->ui_id);
    	gtk_ui_manager_remove_action_group (manager, data->action_group);

    	g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, NULL);
    }

#### Adding menu entries (Python)

    import gtk

    _ui_str = """
    <ui>
    	<menubar name="menubar">
    		<menu name="GoMenu" action="Go">
    			<separator/>
    			<menuitem name="SampleMenuEntry"
    				action="SampleMenuEntry"/>
    			<separator/>
    		</menu>
    	</menubar>
    </ui>
    """

    def _menu_entry_cb(action, data):
    	print data

    _actions = [('SampleMenuEntry', None, 'Sample Menu Entry',
    	     None, None, _menu_entry_cb)]

    def attach_window(window):
    	ui_manager = window.get_ui_manager()
    	group = gtk.ActionGroup('SampleMenu')
    	group.add_actions(_actions, 'Data')
    	ui_manager.insert_action_group(group, 0)
    	ui_id = ui_manager.add_ui_from_string(_ui_str)

    	window._sample_menu_entry_data = (group, ui_id)

    def detach_window(window):
    	group, ui_id = window._sample_menu_entry_data
    	del window._sample_menu_entry_data

    	ui_manager = window.get_ui_manager()
    	ui_manager.remove_ui(ui_id)
    	ui_manager.remove_action_group(group)
    	ui_manager.ensure_update()

The existing epiphany menu hierarchy is described in `epiphany/data/ui/epiphany-ui.xml`.

Without a `placeholder` element in `epiphany/data/ui/epiphany-ui.xml`, extensions can only add menu entries to the top or bottom of menus.

Remember that extensions may be loaded or unloaded at any point in time. The above procedure detaches from windows nicely. If you decide to modify it, be sure that your extension still loads and unloads successfully.

### Statusbar

The statusbar can only display one message at a time. What's more, the statusbar can be disabled. Statusbar messages from one extension can conflict with another — and all extensions can conflict with Epiphany's standard statusbar messages. There are many reasons not to write statusbar messages in your extension.

Writing statusbar messages is one of the easiest things to do from an extension. Just be sure to understand `GtkStatusbar`s: in a given context, there is a stack of messages. Only the top message from all contexts can be shown at a time, and messages can be pushed and popped to and from the stack on any given context.

This sample code should give some ideas:

#### Manipulating the statusbar

    #define STATUSBAR_CONTEXT "EphyFooStatusbarContext"

    static void
    write_statusbar_message (EphyWindow *window, const char *message)
    {
    	GtkStatusbar *statusbar;
    	guint context_id;

    	statusbar = GTK_STATUSBAR (ephy_window_get_statusbar (window));
    	g_return_if_fail (GTK_IS_STATUSBAR (statusbar));

    	context_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (window),
    							  STATUSBAR_CONTEXT));
    	g_return_if_fail (context_id != 0);

    	/* Keep a maximum of one element on the stack */
    	gtk_statusbar_pop (statusbar, context_id);

    	/* Write our message */
    	gtk_statusbar_push (statusbar, context_id, message);
    }

    static void
    remove_statusbar_message (EphyWindow *window)
    {
    	GtkStatusbar *statusbar;
    	guint context_id;

    	statusbar = GTK_STATUSBAR (ephy_window_get_statusbar (window));
    	g_return_if_fail (GTK_IS_STATUSBAR (statusbar));

    	context_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (window),
    							  STATUSBAR_CONTEXT));
    	g_return_if_fail (context_id != 0);

    	gtk_statusbar_pop (statusbar, context_id);
    }

    static void
    impl_attach_window (EphyExtension *extension,
    		    EphyWindow *window)
    {
    	GtkStatusbar *statusbar;
    	guint context_id;

    	statusbar = GTK_STATUSBAR (ephy_window_get_statusbar (window));
    	g_return_if_fail (GTK_IS_STATUSBAR (statusbar));

    	context_id = gtk_statusbar_get_context_id (statusbar, "Foo extension");

    	g_object_set_data (G_OBJECT (window), "foo-statusbar-cid", GUINT_TO_POINTER (context_id));
    }

    static void
    impl_detach_window (EphyExtension *extension,
    		    EphyWindow *window)
    {
    	remove_statusbar_message (window);
    }

### GConf Settings

Using `eel-gconf-extensions.h`, retrieving Epiphany's GConf settings is a snap. The keys used by Epiphany are all listed in `epiphany/lib/ephy-prefs.h` (but ephy-prefs.h is not installed alongside Epiphany, and so cannot be included by your extension). For example, the following function will return whether or not the statusbar is to be displayed:

#### Retrieving a GConf value

    static gboolean
    is_statusbar_wanted (void)
    {
    	return eel_gconf_get_boolean ("/apps/epiphany/general/show_statusbar");
    }

Even better, how about a callback for when a GConf key is changed:

#### Listening to GConf

    struct EphyFooExtensionPrivate
    {
    	/* ... */
    	guint gconf_cnxn_id;
    	/* ... */
    };

    static void
    my_gconf_cb (GConfClient *client,
    	     guint cnxn_id,
    	     GConfEntry *entry,
    	     gpointer user_data)
    {
    	/* Do something */
    }

    static void
    ephy_foo_extension_init (EphyFooExtension *extension)
    {
    	/* ... */
    	extension->priv->gconf_cnxn_id = eel_gconf_notification_add
    		("/apps/epiphany/general/show_statusbar", (GConfClientNotifyFunc) my_gconf_cb, NULL);
    }

    static void
    ephy_foo_extension_finalize (EphyFooExtension *extension)
    {
    	if (extension->priv->gconf_cnxn_id != 0)
    	{
    		eel_gconf_notification_remove (extension->priv->gconf_cnxn_id);
    		extension->priv->gconf_cnxn_id = 0;
    	}
    	/* ... */
    }

Voila! `my_gconf_cb` will be called when the GConf key is modified from within any program. Isn't GConf great?

Read `epiphany-extensions/include/eel-gconf-extensions.h` for a full list of functions.

If your extension is written in Python, you may use gnome-python's GConf bindings.

### Your Own Ideas

Many of the Epiphany integration ideas above were first discovered (or in some cases, the integration functionality was coded into Epiphany itself) by the author while creating the Popup Blocker extension.

While writing an extension, you will almost certainly need to keep looking around `epiphany/src/ephy-window.c`, `epiphany/src/ephy-tab.c`, `epiphany/embed/ephy-embed.h`, and others. The code is very clean; if you still have questions, IRC is a great place to ask (see ).

You may come up with great ideas when writing your own extension. Feel free to email the author some sample code; a new section could be added to this document to help other extension writers.

### Hacking at Epiphany

Extensions can do a heck of a lot. Since the system is still under development, new (good) ideas would likely be welcome. Mozilla integration, for example, is a bit loose. To get certain signals from Mozilla, you may have to hack at files within the `epiphany/embed/` directory in the Epiphany source code. If your patch is clean and logical, submit it to GNOME's Bugzilla and you'll have a good chance of its getting applied.

Stylesheet manipulation is one very important missing feature (all the more important because Mozilla Firefox's extensions are so powerful in this respect). At the time of this document's writing, the author is hacking at Epiphany to improve CSS manipulation from within extensions (and Epiphany itself, for that matter).

Are you an uber-hacker? Here are some challenges. Note: very possibly, a perfectly acceptable patch would not be applied to the official Epiphany source tree. Consider discussing on developer communication channels about how you would modify Epiphany; you will probably get some helpful advice.

- A JavaScript extension system similar to Firefox's: this would naturally be integrated much more tightly with Mozilla, and loosely (if at all) with the Epiphany UI.

  Note: Some such JavaScript-heavy, UI-free extensions actually work fine with Epiphany already (flashblock is one such extension).

- A C# extension loader. The epiphany-mono directory in GNOME CVS has the beginnings of such a system. There are a few bugs, and there is no documentation (this document, for instance, should be updated to include C#).

- [Insert your favourite Firefox extension here. Then make it better.]