From 9a7e41d3ea966387eff8abd86198812244cd47bc Mon Sep 17 00:00:00 2001 From: Vincent Sanders Date: Sun, 7 Mar 2021 22:38:41 +0000 Subject: add document outlining new frontend development --- docs/development.md | 6 + docs/implementing-new-frontend.md | 352 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 358 insertions(+) create mode 100644 docs/implementing-new-frontend.md diff --git a/docs/development.md b/docs/development.md index 5a8d1aa86..3f08b5496 100644 --- a/docs/development.md +++ b/docs/development.md @@ -69,6 +69,12 @@ NetSurf [integration tests](docs/integration-testing.md) use the monkey frontend to operate the browser as a whole. These tests open windows, navigate to websites and render contents as a user might. +# New frontend development + +[Implementing a new frotend](docs/implementing-new-frontend.md) for a +toolkit can be challenging and this guide provides an overview and +worked example. + # Documented API The NetSurf code makes use of Doxygen for code documentation. diff --git a/docs/implementing-new-frontend.md b/docs/implementing-new-frontend.md new file mode 100644 index 000000000..03e6180e2 --- /dev/null +++ b/docs/implementing-new-frontend.md @@ -0,0 +1,352 @@ +# Implementing a new frontend + +[TOC] + +## Introduction + +NetSurf is divided into a series of frontends which provide a user +interface around common core functionality. + +Each frontend is a distinct implementation for a specific GUI toolkit. + +The existing frontends are covered in the [user +interface](docs/user-interface.md) documentation. + +Note implementing a new frontend implies using a toolkit distinct from +one of those already implemented and is distinct from porting NetSurf +to a new operating system platform. + +It is recommend, in the strongest terms, that if the prospective +developer is porting to both a new platform and toolkit that they +*start* by getting the [monkey](docs/using-monkey.md) frontend +building and passing at least the basic integration tests on their +platform. + +Experience has shown that attempting to port to a platform and +implement a toolkit at the same time generally results in failure to +achieve either goal. + +NetSurf is built using GNU make and frontends are expected to +integrate with this buildsystem. + +Implementation languages have historically been limited to C, C++ and +objective C. However any language that can call C functions and +*importantly* be called back from C code ought to be usable. For +example there have been experiments with JAVA using JNI but no current +frontend is implemented using it. + +## Implementation complexity + +An absolutely minimal "proof of concept" frontend implementation (like +the FLTK frontend that will be used as an example) is around 1,000 +lines of C code. Basic functionality like the windows frontend is +around 7,000 lines. A complete fully functional frontend such as the +one for GTK is closer to 15,000 lines. + +It should be noted the majority of the minimal implementation can +simply be copied and the names changed as appropriate from an existing +example. The actual amount of new code that needs to be provided is +very small. + +NetSurf provides a great deal of generic functionality for things like +cookie, bookmark, history windows which require only minimal frontend +support with the [core window API](docs/core-window-interface.md). + +A frontend developer is free to implement any and all of this generic +functionality thelselves in a manner more integrated into a toolkit. + +## Implementation + +A frontend is generally named for the toolkit it is implementing (i.e +gtk for the GTK+ toolkit). It is advisable to be as specific as +possible e.g. the frontend for the windows operating system should +have been named win32 allowing for an impementation using a differnt +toolkit (e.g mfc) + +All the files needed for the frontend are contained in a single +sub-directory in the NetSurf source tree e.g. `frontends/fltk` + +The only file outside this directory that much be changed is the +`frontends/Makefile.hts` where a new entry must be added to the valid +targets list. + +### Build system + +A frontend must provide three GNU Makefile fragments (these will be +included from the core Makefile): + + - `Makefile` - This is used to extend CFLAGS, CXXFLAGS and LDFLAGS variables as required. The executable target is set with EXETARGET and the browser source files are listed in the SOURCES variable + - `Makefile.defaults` - allows setting frontend specific makefile variables and overriding of the default core build variables. + - `Makefile.tools` - allows setting up frontend specific build tooling (as a minimum a tool for the package configuration in PKG_CONFIG) + +Source code modules can be named as the devloper desires within the +frontend directory and should be added to the SOURCES variable as +desired. + +### Program entry + +Generally the entry point from the OS is the `main()` function and +several frontends have a `main.cpp` where some have used `gui.c`. + +The usual shape for the `main()` function is a six step process: + 1. The frontends operation tables are registered with NetSurf + 2. The toolkit specific initialisation is performed (which may involve calling NetSurf provided utility functions for support operations like logging, message translations etc.) + 3. Initialise the NetSurf core. After this point all browser functionality is available and registered operations can be called. + 4. Perform toolkiit setup, usually opening the initial browsing window (perhaps according to user preferences) + 5. Run the toolkits main loop while ensuring the Netsurf scheduled operations are also run at teh apropriate time. + 6. Finalisation on completion. + +### NetSurf operations tables + +The frontend will generally call netsurf interfaces to get a desired +behaviour e.g. `browser_window_create()` to create a new browsing +context (the `browser_window_` prefix is historical and does not +necessarily create a window e.g. on gtk it is more likely to open a +tab in an existing window). To achive the desired operation some +operations need to be performed by the frontend under control of +NetSurf, these operations are listed in tables. + +The operation tables should be registered with the NetSurf core as one +of the first operations of the frontend code. The functions in these +tables (and the tables themselves) must remain valid until +`netsurf_exit()` is called. + +There are (currently) twelve sets of operation tables held in separate +structures. Only five of these are mandantory (misc, window, fetch, +bitmap and layout). + +In this context mandantory means the tables must be non NULL and do +not have a suitable default. Each of the mandantory sets contain +function pointers to implement operations. + +#### misc operation table + +The only mandantory operation in this table is schedule. + +When schedule is called the frontend must arrange for the passed +callback to be called with the context parameter after a number of +miliseconds. + +This callback is typicaly driven through the toolkits event loop and +it is important such callbacks are not attempted from an operation. + +#### window operation table + +The window operations (poorly named as already mentioned) are where +the frontend is called to actually manipulate widgets in the +toolkit. This is mediated through a `gui_window` context pointer which +is typed as a structure. + +This context pointer is passed to all window operations and is +generally assumed to contain at least a reference to the underlying +`browser_window` which is provided in the initial create operation to +allow subsequent use of various core functionality. + +The mandantory window operations are: + - create - create a new browsing context widget in the frontend toolkit + - destroy - destoy a previously created `gui_window` + - invalidate - mark an area of the browsing context viewport as requiring redraw (note no redraw should be attempted from here) + - get_scroll - get the scroll offsets from the toolkit drawing widget + - set_scroll - set the scroll offsets on the toolkit drawing widget + - get_dimensions - get the dimensions of the toolkit drawing widget + - event - deal with various window events from netsurf which have no additional parameters + + +#### fetch operation table + +The fetch operations allow the built in scheme fetchers (file, about, resource) to obtain additional information necessary to complete their operation. + +The two mandantory operations are: + - `filetype` - allows the file scheme to obtain a mime type from a file path e.g. `a.file.name.png` would result in `image/png` + - `get_resource_url` - maps resource scheme paths to URL e.g. `resource:default.css` to `file:///usr/share/netsurf/default.css` + +#### bitmap operation table + +The bitmap table and all of its operations are mandantory only because +the empty defaults have not been included as it is assumed a browser +will want to display images. + +All operations may be provided by stubs that return the failure codes +until full implementations are made. + +#### layout operation table + +The layout table is used to layout text. All operations are given +strings to manipulate encoded in UTF-8. There are three mandantory +operations: + - `width` - Calculate the width of a string. + - `position` - Find the position in a string where an x coordinate falls. + - `split` - Find where to split a string to make it fit a width. + +## Worked Example + +Rather than attempt to describe every aspect of an implementation we +will rather work from an actual minimal example for the FLTK toolkit. + +This is availble as a single commit (`git show 04900e82e65f8669675538a66a01b56a3e473cb2`) in the NetSurf source repository. Alternatively it can be [viewed in a web browser](https://git.netsurf-browser.org/netsurf.git/commit/?h=vince/fltk&id=04900e82e65f8669675538a66a01b56a3e473cb2). + +This represents the absolute minimum implementation to get a browser +window on screen (and be able to click visible links). It is +implemented in C++ as that is the FLTK implementation language but an +equivalent implementation in other languages should be obvious. + +### Building + +The [frontends/Makefile.hts](https://git.netsurf-browser.org/netsurf.git/diff/frontends/Makefile.hts?h=vince/fltk&id=04900e82e65f8669675538a66a01b56a3e473cb2) +had the fltk target added to the VLDTARGET variable. This allows +NetSurf to be built for this frontend with `make TARGET=fltk` + +As previously described the three GNU Make files are added: + +[Makefile](https://git.netsurf-browser.org/netsurf.git/diff/frontends/fltk/Makefile?h=vince/fltk&id=04900e82e65f8669675538a66a01b56a3e473cb2) +this shows how the flags are extended to add the fltk headers and +library. Additionaly the list of sources are built here, as teh +comment suggests it is important the SOURCES variable is not expanded +here so the S_FRONTEND variable is used to allow expansion at teh +correct time in the build process. + + +[Makefile.defaults](https://git.netsurf-browser.org/netsurf.git/diff/frontends/fltk/Makefile.defaults?h=vince/fltk&id=04900e82e65f8669675538a66a01b56a3e473cb2) +has the default setting to control the build parameters and file locations. These can be overriden by the `Makefile.config` at compile time. + +[Makefile.tools](https://git.netsurf-browser.org/netsurf.git/diff/frontends/fltk/Makefile.tools?h=vince/fltk&id=04900e82e65f8669675538a66a01b56a3e473cb2) +allows the configuration of additional tools necessary to build for the target as a minimum pkg-config is usually required to find libraries. + +### Program entry + +In our example program entry is the classical `main()` in the [main.cpp](https://git.netsurf-browser.org/netsurf.git/diff/frontends/fltk/main.cpp?h=vince/fltk&id=04900e82e65f8669675538a66a01b56a3e473cb2) module. + +This implements the six stage process outlined previously. + +#### Operations table registration + +The `netsurf_table` structure is initialised and passed to +`netsurf_register()`. It should be noted that the approach taken here +and in most frontends is to have a source module for each operation +table. The header for each module exposes just the pointer to the +indivial operation set, this allows for all the operation functions to +be static to their module and hence helps reduce global symbol usage. + +#### Frontend specific initialisation + +Her it is implemented in `nsfltk_init()` this function performs all +the operations specific to the frontend which must be initialised +before netsurf itself. In some toolkits this would require calling the +toolkit initialisation (e.g. `gtk_init()`). + +It is nessesary to initialise netsurf logging and user options at this +point. A more fully featured implementation would also initialise the +message translation system here. + +#### Netsurf initialisation + +This is simply the call to `netsurf_init()` from this point the +browser is fully operational and operations can and will be called. + +#### Frontend specific startup + +Although the browser is running it has not yet been told to open a +window or navigate to a page. Here `nsfltk_start()` examines the +command line and environment to determine the initial page to navigate +to and calls `browser_window_create()` with the url, this will cause +the browser to open a new browsing context and start the navigation. + +A frontend may choose to implement more complex logic here but the +example here is a good start. + +#### Toolkit run loop + +The function `nsfltk_run()` runs the toolkit event loop. In this case it is using the generic scheduleing in the [misc.cpp](https://git.netsurf-browser.org/netsurf.git/diff/frontends/fltk/misc.cpp?h=vince/fltk&id=04900e82e65f8669675538a66a01b56a3e473cb2) module to ensure callbacks get made at the apropriate time. + +There is a `nsfltk_done` boolean global checked here so when all the +browser windows are closed the program will exit. + +A more fully featured port might use the toolkits scheduling rather +than open coding a solution with a linked list as is done +here. + +A futher optimisation would be to obtain the set of file descriptors +being used (with `fetch_fdset()`) for active fetches allowing for +activity based fetch progress instead of the fallback polling method. + +#### finalisation + +This simply finalises the browser stopping all activity and cleaning +up any resource usage. After the call to `netsurf_exit()` no more +operation calls will be made and all caches used by the core will be +flushed. + +If user option chnages are to be made persistant `nsoption_finalise()` +should be called. + +The finalisation of logging will ensure that any output buffers are +flushed. + +### The window operation table + +Amongst all the boilerplate of the default implementation the only novel code is in the window operation table in the [window.cpp](https://git.netsurf-browser.org/netsurf.git/diff/frontends/fltk/window.cpp?h=vince/fltk&id=04900e82e65f8669675538a66a01b56a3e473cb2) module. + +#### `nsfltk_window_create` + +The create operation instansiates a new `NS_Window` object and +references it in the gui_window structure which it returns to the +caller. Technically we could simply return the `NS_Window` object as +the gui_window pointer but this implementation is avoiding the cast. + +Secondly `Fl_Double_Window` is subclassed as `NS_Widget`. The sublass +allows the close callback to be accessed so the global `nsfltk_done` +boolean can be set during the destructor method. + +The NS_Window creates an instance of `NS_Widget` in its constructor, a +more extensive implementation would add other window furniture here +(scroll bars, url bar, navigation elements, etc.) + +The implementation subclasses `Fl_Widget` implementing the draw +method to render the browsing context and the handle method to handle +mouse events to allow teh user to click. + +The `NS_Widget::handle()` method simply translates the mouse press +event from widget coordinates to netsurf canvas cooridinates and maps +teh mouse button state. The core is informed of these events using +`browser_window_mouse_click()` + +The `NS_Widget::draw` method similarly translates the fltk toolkits +clip rectangle, builds a plotting context and calls +`browser_window_redraw()` which will use the plotting operations in +the plotting context to render the browsing context within the area +specified. One thing to note here is the translation between the +coordinates of the render area and the internal page canvas given as +the second and third parameters to the draw call. When scrolling is +required this is achived by altering these offsets. + + +#### `nsfltk_window_invalidate()` + +This simply calls the damage method on the `Fl_Widget` class with the +appropriate coordinate translation. + +#### `nsfltk_window_get_dimensions()` + +This obtains the fltk widget width and height and returns them. + +### The plotting interface + +When the `NS_Widget::draw` method was discussed it was noted that a +plotting context is built containing an operation table. That table is +implemented in [plotters.cpp](https://git.netsurf-browser.org/netsurf.git/diff/frontends/fltk/plotters.cpp?h=vince/fltk&id=04900e82e65f8669675538a66a01b56a3e473cb2) + +The implementation here is as minimal as can be, only line, rectangle +and text have any implementation at all and even that simply sets a +colour and performs the appropriate fltk draw function (`fl_line`, +`fl_rect` and `fl_draw` respectively) + +## Conclusion + +Hopefully this breif overview and worked example should give the +prospectinve frontend developer enough information to understand how +to get started implementing a new frontend toolkit for NetSurf. + +As can be seen there is actualy very little novel code necessary to +get started though I should mention that the move from "minimal" to +"full" implementation is a large undertaking and it would be wise to +talk with the NetSurf developers if undertaking such work. -- cgit v1.2.3