Open main menu

CDOT Wiki β

Implementing the Mouse Lock API in Firefox

Revision as of 20:05, 8 December 2011 by Cwtseng (talk | contribs) (Using the Test Harness)

Introduction

This is a working document for the implementation of the Mouse Lock API spec in Mozilla by students in David Humphrey's Mozilla Development class at Seneca College.

Please add, edit, correct, expand, etc. as necessary. This page should contain any links or other info we need.

Participants

While the project is primarily meant for students in DPS909/OSD600, feel free to join us if you want to work on things.

  • David Humphrey (lead developer, professor, @humphd)
  • Hasan Kamal-Al-Deen (tardy student, @NorthWind87)
  • Matthew Schranz (Student, OSD600, @mjschranz)
  • Yevgeniy Ivanchenko (Student, OSD600)
  • Chris Gosselin (Student, OSD600)
  • Anurag Bhatnagar (Student, DPS909, @anuragbh)
  • Raymond Hung (Wannabe Developer, Student, @Raymond_Hung)
  • Ausley Johnson(Student, OSD600)
  • Jesse Silver (Student, OSD600)
  • Ching Wei Tseng(Student, DPS909)
  • Michelle Mendoza (Student, DPS909)
  • Archana Sahota (Student, DPS909)
  • Greg Krilov (Student, DPS909)
  • Roman Hotin (Student, DPS909)
  • Sergiu Ecob (Student, OSD600)
  • Jordan Raffoul (Student)
  • Hyungryul Chun (Student, DPS909)
  • James Boelen (Masked Crusader, @jamesboelen)
  • Jacky Siu (Student, OSD600)
  • Abhishek Bhatnagar (Student, @abhishekToronto)
  • Diogo Golovanevsky Monteiro (@diogogmt)
  • Simon de Almeida(Student) (@simon661)
  • Stanley Tsang (Student, DPS909)
  • Denise Rigato (Student, DPS909)
  • Qian (Ken) Xu (Student, DPS909)
  • Moussa Tabcharani (Student, DPS909)
  • Keyan Ren (Student, OSD600)
  • Joseph Hughes (Student, OSD600)

Communication

Development work will be done using a combination of the following:

Tasks

Getting Started

  1. Clone our repo and build a debug version locally
  2. Get a https://bugzilla.mozilla.org account and CC yourself on the bug.
  3. Set a Watch on this page and the Q/A page so you know when things change.
  4. Break the spec down into an itemized list of things we need to do, tests we need to write, features we have to add, edge cases we have to worry about, demos we need to build, etc. Put the info into this page. We need to know everything we'll have to write and schedule when we'll do each bit.
  5. Blog about your work on this implementation
  6. Add questions/answers to Mouse Lock Implementation FAQ

High-Level Mouse Lock Implementation Tasks

Implementation

  1. No mouse cursor is displayed when the mouse is locked - rhung
  2. MouseLockable DOM Implementation, navigator.pointer (Notes on MouseLock DOM Implementation Nov 13, 2011) - humph
    1. void lock (in Element target, optional in VoidCallback successCallback, optional in VoidCallback failureCallback); diogogmt, humph
    2. void unlock (); humph, diogogmt
    3. bool islocked (); - humph
  3. Mouse Lock Platform Implementations JSilver999, humph
    1. Windows: SetCursorPos(x, y)
    2. OS X CGWarpMouseCursorPosition(CGPointMake(x, y))
    3. Linux (GTK) gdk_display_warp_pointer (display, screen, x, y), add to http://mxr.mozilla.org/mozilla-central/source/widget/src/gtk2/nsWindow.cpp ???
    4. Mobile?
  4. Investigate whether we can get movement information directly from OS</strke> - northWind
  5. mouselocklost event DOM Implementation - diogogmt
  6. Extend MouseEvent DOM implementation with movementX, movementY - humph, JSilver999
    1. Stack Trace for nsDOMMouseEvent::nsDOMMouseEvent ctor (created on mouse move)
    2. Stack Trace for nsDOMMouseEvent::GetScreenX
    3. Where to store the state info (e.g., previous position) between mouse events?
  7. The browser must exit the mouse lock state if the user agent, window, or tab loses focus - diogogmt
  8. <strike>Fixup NULLs being returned from lock() C++ implementation, should be NS* - Anachid
  9. Mouse lock should only work when in Full Screen Mode - diogogmt, rhung
  10. Refactor nsIDOMNavigator changes for pointer attribute to be in separate interface humph
  11. Do we need to do conditional compilation for mouse lock? humph (not going to bother for now)
  12. When mouse lock is enabled clientX, clientY, screenX, and screenY must hold constant values as if the mouse did not move at all once mouse lock was entered. humph
  13. Freeze mouse pointer in centre of window when mouse lock is enabled (e.g., moving the mouse causes an event, but forces the mouse to go back to the original position). - JSilver999
  14. "Events that require the concept of a mouse cursor must not be dispatched (for example: mouseover, mouseout)" - humph
  15. Figure out Mac Crash with Jesse's SynthesizeMouseMove change humph
  16. When the locked element is removed from the DOM Tree, the mouse should be unlocked diogogmt
  17. Save the screenX and screenY position before locking the mouse. - humph
  18. Reset the mouse position back to the original position when unlocking. - humph
  19. "When unlocked, the system cursor can exit and re-enter the user agent window. If it does so and the user agent was not the target of operating system mouse move events then the most recent mouse position will be unknown to the user agent and movementX/Y can not be computed and must be set to zero" diogogmt
  20. Trying to lock a locked element should fire the success callback CloudScorpion
  21. Before locking the mouse check if the element is a DOM element and if it is in the DOM Tree diogogmt
  22. Fix accurateness of mouse positioning on unlock() (should be the same point as when lock() was called). Currently works, but is offset. See nsEventStateManager::SetMouseLock. JSilver999
  23. Restructure Lock method to do most of its operations in a separate thread. humph
  24. Fix license headers for new files to use proper MPL boilerplate humph
  25. Do we need to add a user pref to enable/disable mouse lock? Nice to have, not blocking. northwind, mjschranz
  26. "Once mouse lock is acquired, stop mouse events from being fired to other elements that are not locked (e.g., only fire to locked element)." Only the fullscreen element will get events. Need advice in review on how to do this properly.
Out of Scope Implementation

Because we are only implementing Mouse Lock for Fullscreen elements, some aspects of the spec can/must be put off until later. Other items below are simply not in scope for this first round of implementation.

  1. The ESC key should exit mouse lock. This will currently exit fullscreen, and therefore mouse lock - diogogmt
  2. "User agents may prompt for confirmation before locking, this preference may be saved as a content setting" How to deal with this? What UI do we use? See also, "Repeated escapes of mouse lock can signal user agent to not re-lock the mouse without more specific user intent gesture, e.g. similar to how Chrome suppresses repeated alert() calls"
  3. "The Mouse Lock API must exit the mouse lock state if the user agent, window, or tab loses focus"
  4. Clip the mouse so it doesn't leave the locked element with a mouse movement large enough to exceed its bounds. See:
    1. ClipCursor on Windows
    2. CGAssociateMouseAndMouseCursorPosition and CGGetLastMouseDelta on OS X
    3. XGrabPointer on Linux

Tests

The central repo with all the tests are located here. For more information on how to make mochitests and how to send the pull request to the appropriate person, check out the Mochitest FAQ List tests we need below:

  1. "there is no limit to how far movement can go...not limited by screen boundaries" -- mouse lock should mean infinite movement in the X and Y axes...There will be no limit to movementX/Y values if the mouse is continuously moved in a single direction" - hchun
  2. "The concept of the mouse cursor will have been removed, and it will not move off the window or be clamped by a screen edge" - hchun
  3. "no mouse cursor is displayed" -- mouse cursor should be hidden while locked - dvillase
  4. navigator.pointer (readonly) is a MouseLockable - abhatnagar
  5. MouseLockable has lock(), unlock(), islocked() - abhatnagar
  6. islocked() returns true if mouse is locked, false if not locked - JSilver999
  7. lock(target) expects a DOM element, and takes two optional callbacks: successcallback, failurecallback. - JSilver999
  8. lock() should return immediately and call callbacks when lock succeeds or fails - JSilver999
  9. "Mouse lock must succeed only if the window is in focus" - johnno
  10. "Mouse lock must succeed only if...the user-agent is the active application of the operating system" - johnno
  11. "The target of lock need not be in focus" - johnno
  12. "Mouse lock must succeed only if the target is in the DOM tree" - Anachid
  13. "If the target is removed from the DOM tree after mouse lock is entered then mouse lock will be lost." - Anachid
  14. "If the mouse is already locked to the same element, a repeated call to lock will succeed and the successCallback called" - jboelen
  15. "If another element is locked [and lock() is called] a user agent must transfer the mouse lock to the new target and call the mouselocklost callback for the previous target" - jboelen
  16. "The Mouse Lock API must provide a default system action to unlock the mouse" namely ESC. - CloudScorpian
  17. "Once in the locked state the user agent must fire all relevant user generated MouseEvent events (for example: mousemove, mousedown, mouseup, click, wheel)[DOM-LEVEL-3-CORE] to the target of mouse lock, and not fire mouse events to other elements" - rhung
  18. "Events that require the concept of a mouse cursor must not be dispatched (for example: mouseover, mouseout)" - rhung
  19. "Movement and button presses of the mouse must not cause the window to lose focus" -
  20. "Synthetic mouse events created by application script act the same regardless of lock state" - Tentacle
  21. "The unlock method cancels the mouse lock state" - abhatnagar1
  22. "[Upon unlock() t]he system mouse cursor must be displayed again and positioned at the same location that it was when mouse lock was entered (the same location that is reported in screenX/Y when the mouse is locked)" - dvillase
  23. "When mouse lock is lost or disabled for any reason user agents must fire an event named mouselocklost with its bubble attribute set to true to the mouse lock target element" - stsang
  24. MouseEvent must contain (readonly) movementX and movementY - KeyR, JSilver999
  25. "The members movementX and movementY must provide the change in position of the mouse, as if the values of screenX/Y were stored between two subsequent mousemove events eNow and ePrevious and the difference taken movementX = eNow.screenX-ePrevious.screenX" - KeyR, JSilver999
  26. "movementX/Y must be valid regardless of mouse lock state" - KeyR, JSilver999
  27. "When unlocked, the system cursor can exit and re-enter the user agent window. If it does so and the user agent was not the target of operating system mouse move events then the most recent mouse position will be unknown to the user agent and movementX/Y can not be computed and must be set to zero" - moussa1
  28. "When mouse lock is enabled clientX, clientY, screenX, and screenY must hold constant values as if the mouse did not move at all once mouse lock was entered" - jbraffoul
  29. "The Mouse Lock API must exit the mouse lock state if the user agent, window, or tab loses focus" - drigato
  30. Test to make sure that mouse lock only occurs when an element is in full screen mode (not F11 or done via the menus). This includes:
    1. Switching focus to another window - jsiu3
  31. Tests for mouselocklost event - stsang
Reviewing Tests

There are a series of common mistakes in the tests that need to get fixed. Here are some of them:

  1. Need to clean-up tabs vs. spaces and indentation issues (2-spaces per tab) in many test files. Use https://github.com/einars/js-beautify. This is a test that is formatted correctly, in terms of indentation and spaces vs. tabs.
  2. No line of code should be 80 characters or longer--break them so they are under 80
  3. No Windows end-of-lines, use Unix end-of-lines
  4. No whitespace at the end of a line
  5. Run your test code through http://www.jshint.com/. Note, it will complain about unknown globals like SimpleTest. Make sure the rest of the JavaScript is good.
  6. Remove unnecessary comments. Only things that explain the test.
  7. If you need constants, use const instead of var
  8. Don't use variables if they aren't needed. For example, don't introduce a variable to store a value, only to pass it into ok() or is(). Just test the expression in ok() or is().
  9. Make sure your JavaScript follows proper naming: goodVariableName, bad_variable_name, badvariablename;
  10. Tests should follow the template as closely as makes sense: http://pastebin.com/vxmsepVh
  11. Tests should include a simple comment block describing what is being tested
  12. Only use SimpleTest.waitForFocus() if you really need it.
  13. Be consistent with "..." vs. '...' for strings. Pick one and use it throughout the file.
  14. Remove console.log()
  15. Prefer document.body to document.getElementsByTagName('body')[0]
  16. Braces on the same line: if (...) {\n and } else {\n
  17. An error message for is() or ok() of "Error message" is not acceptable. Make sure you understand the failure.
  18. If you need to actually lock the mouse, you'll need SpecialPowers.setBoolPref("full-screen-api.allow-trusted-requests-only", false); to allow non-user initiated fullscreen mode (i.e., normally it requires a user to click a button or trigger some other event).
  19. If you're testing that variable foo or expression fooFunction() are true, use ok(), not is(): ok(foo, "Error message if not true."); or ok(fooFunction(), "Error message if function returns false");. Don't do is(foo, true, "Error message if false.");
  20. Make sure code that relies on things happening in asynchronous code gets called in a callback or event handler, not on the next line. For example, if you call navigator.pointer.lock(), you can't check navigator.pointer.islocked() on the next line, you need to use the successCallback/errorCallback. Same for focus or blur calls and events. Actions that trigger something happening in the future often need a callback or event handler.
  21. Prefer <code>i++; to i+=1;

One or more tests have to deal with switching the lock between multiple elements, and the spec is changing on that front.

Test Reviews

The following tests need review and/or fixes, and to then get updated in rhung's mouslock-tests branch on github. Please add your name beside test(s) you are reviewing/fixing. Once a test is complete and updated in his branch, please cross it off below.

Num Name Owner Has Harness Rev. Peer Rev. Pull Contrib.
Ensure you have actually tested your test before sending a pull request
Fill Out the Contrib cell with "First Last <email>" or leave blank for none.
1 test_FullScreenHarness.html Anachid X Ching Wei Tseng (Steven) <steven_tseng15@hotmail.com>
2 mouselock_util.js Anachid X Ching Wei Tseng (Steven) <steven_tseng15@hotmail.com>
3 file_DOMtree.html Anachid X Ching Wei Tseng (Steven) <steven_tseng15@hotmail.com>
4 test_MouseEvents.html rhung
5 test_TargetOutOfFocus.html johnno
6 test_UserAgentIsActive johnno
7 test_constantxy.html jbraffoul
8 test_defaultUnlock.html KeyR X nm486
9 test_doubleLockCallBack.html jboelen X rhung
10 test_exitMouselockOnLoseFocus.html drigato / johnno
11 test_isInstanceofMouselockable.html abhatnagar X
12 test_limitlessScroll.html hchun X
13 test_mouseLockableHasRequiredMethods.html abhatnagar X
14 test_mousePos.html jboelen
15 test_mouselocklost.html nm486
16 test_movement.html
17 test_navigatorPointer.html abhatnagar
18 test_syntheticMouseEvent.html
19 test_userPref.html mjschranz
20 test_fullscreen.html abhatnagar1 x x Abhishek Bhatnagar <abhatnagar1@learn.senecac.on.ca>
21 test_syntheticMouseEvent.html Tentacle x x x x Qian Xu <qxu26@learn.senecac.on.ca>


  1. test_FullScreenHarness.html - Anachid
  2. mouselock_util.js - Anachid
  3. file_DOMtree.html - Anachid
  4. test_LockWhenOutOfFocus.html Note: Removed
  5. test_MouseEvents.html - rhung
  6. test_TargetOutOfFocus.html - johnno
  7. test_UserAgentIsActive.html - johnno
  8. test_constantxy.html - jbraffoul
  9. test_defaultUnlock.html
  10. test_doesunlock.html Note: Removed, redundant;
  11. test_doubleLockCallBack.html
  12. test_exitMouselockOnLoseFocus.html drigato,johnno(merged)
  13. test_fullscreen.html
  14. test_isInstanceofMouselockable.html - abhatnagar
  15. test_limitlessScroll.html - hchun
  16. test_lockLostCallBack.html
  17. test_mouseLockableHasRequiredMethods.html - abhatnagar
  18. test_mousePos.html
  19. test_mouselocklost.html
  20. test_movement.html -hchun
  21. test_navigatorPointer.html - abhatnagar
  22. test_syntheticMouseEvent.html
  23. test_userPref.html - mjschranz

Using the Test Harness

Note: This harness assumes that your test runs properly as a stand-alone application and is not being used as one of the test amongst others.

The problem with the test timing out is due to the fact that the Mochitest iframe does not allow fullscreen from the html code inside the frame. To solve this problem, it needs a small work around like using the harness.

To make use of the harness, you will need to add the mouselock_util.js file to your test html file. I would recommend renaming your file to file_something.html (vs. test_something.html), as this denotes that this not a direct test under the mochitest.

Thus, add the following line, AFTER the EventUtil.js and SimpleTest.js script tag

<script type="application/javascript" src="mouselock_util.js"></script>

Another, thing that needs to be done is to add your file to the gTestFiles array inside the test_FullScreenHarness.html file

After that, do a run of the whole mochitest directory on your machine before doing a pull request. (use this):

TEST_PATH=dom/tests/mochitest/mouselock/ make -C $(replace with OBJDIR) mochitest-plain


Finally, since the fullscreen needs the harness, the harness will provide the special powers. So please no NOT add any special powers in your child files.

Demos, Docs, Other

  1. Proper IDL documentation for navigator.pointer (see example in https://mxr.mozilla.org/mozilla-central/source/dom/interfaces/html/nsIDOMHTMLElement.idl#103), MouseLockable and its methods, MouseLockLost event, etc.
  2. Write a JavaScript library to somehow combine element.mozRequestFullScreen() and navigator.pointer.lock(). It would be good to hide the complexities of doing fullscreen then locking in a single API call.
var canvas = document.getElementById('canvas');

// Will put element into fullscreen then try to do mouse lock
var lock = acquireMouseLock({
  element: canvas,
  onLock: function() {
    // OPTIONAL
    // fullscreen + mouse lock are now setup for element
  },
  onUnlock: function() {
    // OPTIONAL
    // the user or browser took us out of fullscreen/mouse lock
  },
  onError: function() {
    // OPTIONAL
    // there was an error getting fullscreen/mouse lock
  },
  onMovemennt: function(e) {
    // OPTIONAL
    // movementX and movementY for mousemove are passed in
    var deltaX = e.movementX;
    var deltaY = e.movementY;
  }
});

...
lock.islocked; // true or false
lock.unlock();
  1. Mouse lock specification fix requests
  2. Convert Rescue Fox to use Mouse Lock, see https://github.com/mozilla/rescuefox
  3. Convert http://cjcliffe.github.com/CubicVR.js/cubicvr/samples/fps_demo/level1.html to use Mouse Lock - JSilver999
  4. Create a tutorial on how to use Mouse Lock, with code examples
  5. Add demo pages to gh-pages branch
  6. Review https://developer.mozilla.org/en/API/Mouse_Lock_API for correctness with spec + our implementation

Resources