Proudly ProcrasDonating

Technology Thoughts

Extension Listeners

Original extension architecture

The above diagram shows our original extension architecture. It’s not a hacked together mess by any means, but not all of the object oriented behaviors went with the right objects. Mix in some dead or duplicated code from learning how to write extensions and soon it’s hard to keep things straight.

Dan wrote this while I mucked around with views and the data model, so don’t think I’m complaining. It worked! Nonetheless, reading through the code sufficiently confused me so that in the diagram I listed each object’s functions, including who called what, and also who used what preferences, which made the diagram so confusing I then added big red arrows to give the big picture.

Super awesome extension architecture

This diagram shows how our refactored extension architecture currently looks. Beautiful isn’t it? It’s not a true apples-to-apples comparison with the original diagram since this one contains much less information. I suppose that is also good news, since it means I’m more intimately acquainted with the code and APIs, and therefore no longer needed to spell everything out.

Nonetheless, the code has been greatly clarified. Part of the reason I can look at each object and know what it does is because each object does exactly what one would expect. The main improvements are:

  • Listener objects
  • Appropriately placed fields
  • Deleted dead, duplicate code

Listener Objects

Our extension has a number of listeners:

  • InitListener
  • PageLoadListener
  • URLBarListener
  • UninstallListener
  • BlurFocusListener
  • SleepWakeListener
  • IdleBackListener (x2)

In the past couple weeks I’ve added those last three listeners, plus half of the uninstall one. Even though the extension’s main object, myOverlay, originally contained just three listeners, the jumbling of code made it seem super complicated.

All of the non-view and non-lib code was shared between Overlay and PDDB (ProcrasDonateDataBase). This included install and upgrade code, website tracking listeners and logic, database initialization and page load logic.

myOverlay was itself the listener to various events, although it did instantiate a URLBarListener instance that subclassed, in a sense, from Overlay. Some listeners weren’t able to unregister properly. Some of the new listeners wanted to live in PDDB.

myOverlay and PDDB did too much. Now they delegate to happily packaged listeners.

Each group of listeners that make a listening concept, such as “em-action-requested” and “quit-application-granted”, from which we determine whether to uninstall the extension, is now packaged into a handy class. The constructor takes necessary state, such as access to PDDB and preferences, and automatically registers itself.

Additionally, I abstracted all the time tracking logic, such as start_recording and stop_recording, into a TimeTracking object which is passed into the constructors of the time tracking listeners.

Appropriately placed object fields

There’s nothing more confusing than our database ORM also doing time tracking logic and url dispatch. And why did I put the toolbar manager inside the url bar listener?

The new setup is clear as water. Overlay registers the InitListener. The InitListener loads everything else: all of the state (PDDB, TimeTracker, Schedule, Controller, Prefs, PD_API, ToolbarManager); and all of the listeners. PDDB just initializes state. The Controller’s URL dispatch is called from PageLoadListener, just as you’d expect.

When InitListener’s uninit is called, it unregisters all of its listeners.

Deleted dead, duplicate code

Finally, it helps when there is just one Controller instantiation, rather than an array of controllers and various Controller references, including a PageController, which is already a field of Controller. I guess this wasn’t problematic because even though our code is object oriented, there is very little instance state. Boy would that be a hard bug to track down one day…

While refactoring the code, I was able to remove many of the unnecessary variables and functions that come from iterative learning. In one place we used the “nsIPrefService” directly, whereas in most others we used our abstracted PreferenceManager object. Now we use the PreferenceManager everywhere, and not by asking for the one inside PDDB! Same goes for accessing the ProcrasDonate API. No longer does the view have to access it through

this.pddb.page.pd_api

. The view gets its own reference on instantiation, just as PageController does.

Epilogue

I took apart the code yesterday morning. It was a working well oiled machine by yesterday night. According to my meticulous records that meant I spent at most 9 hrs on this story.[1] I spent maybe two days prior informally thinking about the re-factoring I wanted to do (which is meticulously recorded as sleeping and walking with Bear to the park).

It was adding the SleepWakeListener a few days ago that tipped me over into re-factoring. After doubling the number of listeners and having to run the gamut of warning lights and not-quite-working binds, I was ready to make adding features easier and better.

Last night, once I completed putting things back together, I didn’t have any heinous bugs. I wasn’t expecting any, which is an unusual and awesome feeling that I’m chalking up to my excellent understanding of our code and the Mozilla API, rather than some kind of ignorant risk-taking or good fortune. Either way, I closed my laptop, brushed my teeth, and when I came back to admire the beautiful new architecture one last time before bed, ProcrasDonate verified that my last page view had only lasted 5 seconds rather than 5 minutes.

Proudly ProcrasDonating,

Lucy.

ps – We also still pass all of our regression tests. How to test listeners will have to be a different blog post.

pps – I do take meticulous records, which I’m looking forward to blogging about later. [edit: see Life Tracking]

ppps – Wondering what these listeners are? Here’s a simplified explanation

InitListener – Called when a window is loaded or unloaded

PageLoadListener – Called when a page is loaded

URLBarListener – Called when the url bar changes

UninstallListener – Called when the user requests the extension is uninstalled, cancels that request, or closes Firefox, at which point a request uninstall will occur

BlurFocusListener – Called when a window element gains or loses focus (used to track whether Firefox is the active application)

SleepWakeListener – Called when the user’s computer goes to sleep or wakes up

IdleBackListener (x2) – Called when the operating system reports that the user has been idle for a specified period of time (we specify two times, and thus have two listeners)

No comments yet »

Your comment

HTML-Tags:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>