FireBreath Tips: Drawing on Windows
FireBreath Window Abstraction
It is an interesting thing to me that so many people seem to have a hard time understanding how the FireBreath windowing abstraction works, since to me it seems fairly clear. Of course, I wrote it, so that’s probably the reason =] There are a few things you should understand before you start trying to do your drawing on Windows:
Every platform has a PluginWindow. In fact, some platforms (most notably Mac) have several different types of PluginWindow depending on which drawing model and event model you use. The PluginWindow is the source of all system events for the plugin.
In the default plugin skeleton generated by fbgen there are several Events mapped:
BEGIN_PLUGIN_EVENT_MAP() EVENTTYPE_CASE(FB::AttachedEvent, onWindowAttached, FB::PluginWindow) EVENTTYPE_CASE(FB::DetachedEvent, onWindowDetached, FB::PluginWindow) END_PLUGIN_EVENT_MAP()
Then the matching handlers are:
virtual bool onWindowAttached(FB::AttachedEvent *evt, FB::PluginWindow *wnd); virtual bool onWindowDetached(FB::DetachedEvent *evt, FB::PluginWindow *wnd);
FB::AttachedEvent will be fired each time a window is attached to the plugin. FB::DetachedEvent will be fired each time a window is detached from the plugin. NOTE: Though in practice each of these usually get fired only once per plugin instance, there is no guarantee in the browser contract that it won’t take away the window it gave you and give you a different one.
The main plugin object is, by default, a platform agnostic object. There are several ways that you can do your windows specific drawing:
- Make your plugin object windows specific
This is certainly the easiest way, though the least friendly to cross-platform development; you could just change the type of the event source specified in EVENTTYPE_CASE to FB::PluginWindowWin (as well as the type in the handlers) and you will have your object cast in the way you need it.
Note that FB::PluginWindowWin is in
- Create a platform specific subclass of your plugin object
This is actually fairly easy. Simply create an object (Such as MyPluginWin) in the Win/ directory that extends your main (MyPlugin) object and move Factory.cpp into your Win/ directory. If you support other platforms, you’ll need to create a subclass and factory for each platform you want to support. Then modify each Factory.cpp so that createPlugin returns the platform-specific plugin object. Then you can make platform-specific event code to get your PluginWindowWin object.
- Use #ifdef FB_WIN to have platform specific code in your plugin object
Don’t do this. It’s messy and ugly. Still, you can if you want. To get a PluginWindowWin* from your PluginWindow* object, you can do:
FB::PluginWindowWin *pwnd = wnd->get_as<FB::PluginWindowWin*>();
- Create a platform specific object and pass the window and other information to it to draw
Use the same method mentioned previously to convert the PluginWindow to a PluginWindowWin. This is the method that the BasicMediaPlayer example (found in examples/) uses.
Once you have your PluginWindowWin object, you can get the information you need to do the actual drawing with a few functions:
- getHWND() – returns the HWND assigned to the browser plugin
- getBrowserHWND() – returns the HWND of the top-level browser window
- InvalidateWindow() – instructs the browser to invalidate the window and give you a RefreshEvent (fired when a WM_PAINT is received)
When to draw
Once you have the HWND, the next thing you need to know is when you can draw. There are a few guidelines:
- Whenever a RefreshEvent is received, you must redraw. If you are using a secondary thread to draw, make sure you have some way of passing the message to that thread or you will get flickering.
- You can create your own rendering / drawing loop and draw whenever you want. In order to do this, you must create thread to handle the drawing; remember that in the instance of a DetachedEvent you should be able to tell this to stop drawing! If you choose to use something like OpenGL or D3D, you should (sometimes must) do all initialization and drawing on the same secondary thread.
- Remember that these are windowed plugins, which means that the plugin will “float” above everything else on the page.
Handling custom windows messages (your own WINPROC)
If you need to handle the windows messages directly rather than using the FireBreath abstractions, there are two ways to do this.
- Extend PluginWindowWin and override the virtual function CustomWinProc
This is the most direct way; CustomWinProc will get called for all unhandled messages. If you don’t do anything but return false in HandleEvent, all messages will be unhandled. To override which object is created for the PluginWindow, implement the createPluginWindowWin method on your Factory to return your custom window class (which must extend PluginWindowWin)
- Handle WindowsEvent
This is the way that we recommend. This can be done by simply adding an EVENTTYPE_CASE line. All of the arguments given in an event loop are available as members of the WindowsEvent object. This makes it possible to pass the object through to the correct place without having a platform-specialized class, if desired.
FireBreath is flexible
One of the main design goals of FireBreath is to allow a user to extend it however they want to make it work how they are most accustomed; that is why there are so many options. Some restrictions are imposed externally by the nature of browser plugins. In all things, we recommend doing the simplest thing and only override existing classes if you need to — those are the solutions most likely to break with future versions.
As always, if you have questions or need help, feel free to drop into the IRC room!