Implementing Mouse Lock, part III
This post is part of a series I’m writing about my work to implement the Mouse Lock API in Mozilla. I’m doing the work with students in my Mozilla Open Source course at Seneca College, and so theses posts are intentionally didactic and self reflective. The aim of the series is to show how a new feature gets implemented in Firefox. Please note that all code discussed is unreviewed and not part of a shipping Firefox at this point.
In last week's post I outlined set of strategies for beginning the Mouse Lock code, and described our first implementation work to alter navigator, and to add the new MouseLockable DOM type. Since then I began to break the spec into smaller pieces, being careful to not overwhelm the students with everything at first--my plan is to grow this list as we check things off.
In class I did a first run of adding movementX and movementY, including adding a basic test, but didn't commit the work. I asked the students to do this themselves in their own forks, and provide a basic implementation (e.g., returning any number vs. worrying about the correct one). I also encouraged them to try any of the other tasks that interested them, and quite a few people did just that. So far I've seen progress on DOM code for movementX/Y (see Diogo's and Jacky's posts), how to hide the mouse cursor when locked (see Raymond's posts as well as Anurag's), how to deal with complex UI cases in Mochitests, how to exit mouse lock when the Escape key is pressed, how to add the MouseLockLost event, where to store the mouse movement state between events, etc. There's also been more "you find it, you fix it!" happening, this time to Matthew with bug 334573 for which he's testing a patch--welcome to Mozilla!
Many of the experiments they've done are incomplete, inefficient, etc. One of the ideas I taught early in the course was that open source favours Brooks' method of writing code that you will throw away, in order to learn about the process. This has encouraged the students to try things without worrying about whether or not it's perfect--I've literally told them to "bang things in with a hammer" at this stage. That idea is key as you start on code like this when you aren't an expert. You have to experiment, and be willing to throw your work away so that you can build proper implementations once you have a more complete picture and understanding. It's been encouraging to see the students take this to heart, and overcome their hesitation with a) doing it wrong; b) talking about their mistakes; c) asking for help.
I've also been encouraged to see the use of git, github, and blogging playing out as I'd hoped. Reading students saying things like, "I decided to use Diogo’s changes as a springboard..." and then seeing them push and pull branches from one another, all without needing to ask for permission, is exactly how I wanted this to work. I'm also seeing a lot of students going to MXR, DXR, searching bugzilla for old bugs related to things we are doing, and using irc a lot more. Community development practices do work.
For my class tomorrow I decided to finish the movementX and movementY properties, and to hook-up all the wiring to get the proper mouse info into the DOM. Here's a screenshot of what I've done, showing the mousemove event firing and recording screenX, screenY, and the new movementX, movementY (i.e., change since last event) values:
I struggled to find the right place to store the previous event's screenX and screenY values, such that I could calculate a delta for the current position. After examining a lot of stacks for breakpoints in the mouse events code, I noticed that nsEventStateManager always appeared beneath the code I care about. Further investigation showed that other state was being stored here, with member variables named mLastLeftMouseDownContent and mLastLineScrollConsumedX. With a feasible plan, I went to irc to see if I could get validation from others who understand the code. Kyle confirmed my thinking. I emailed Olli to make sure he agreed before I started changing things:
My current thinking is to do it in nsEventStateManager, perhaps in GenerateMouseEnterExit. Here I have nsGUIEvent* aEvent, and I'm wondering about adding something like prevRefPoint to nsEvent, so that I can send the current refPoint along with the last refPoint I saw in the previous event--I'd store a copy of the previous event's refPoint on nsEventStateManager so I'd have it for the next event.
Olli suggested I use a static class variable on nsEventStateManager, but otherwise thought this sounded sane. It worked, and the code is in my movementxy branch.
Tomorrow I hope to merge a bunch of my student's branches into mine, and consolidate our work. We're fast approaching a working demo of the feature, which I hope to have by the end of next week, if not sooner. This will give us time to refine our work in order to start the review process.
One of our next struggles is going to be how to reposition the mouse pointer back to the centre of the screen after a move event. This will allow us to gain infinite scrolling in all directions, while still reporting movement changes between events. I see there is some code for this buried in the tree, but haven't found it for all platforms. It's a mystery I'll probably have something to tell you about next time.