Bug 633602 - Implement Pointer Lock (Mouse Lock) API. r=roc, smaug
authorDavid Humphrey <david.humphrey@senecac.on.ca>
Wed, 28 Mar 2012 21:00:14 -0400
changeset 93893 dcf8929a8115a4a90e673f9dbf5c857857307445
parent 93892 082d5e045cc2009c1b7709b85b2684925759e7d1
child 93894 095fd525afa782e7f4c23a7056b1810f512284ee
push id886
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 19:57:52 +0000
treeherdermozilla-beta@bbd8d5efd6d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc, smaug
bugs633602
milestone14.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 633602 - Implement Pointer Lock (Mouse Lock) API. r=roc, smaug
content/base/public/nsIDocument.h
content/base/public/nsINode.h
content/base/src/nsDocument.cpp
content/base/src/nsDocument.h
content/base/src/nsGenericElement.cpp
content/base/src/nsGkAtomList.h
content/events/public/nsEventNameList.h
content/events/src/nsDOMEvent.cpp
content/events/src/nsDOMEvent.h
content/events/src/nsDOMMouseEvent.cpp
content/events/src/nsDOMUIEvent.cpp
content/events/src/nsDOMUIEvent.h
content/events/src/nsEventStateManager.cpp
content/events/src/nsEventStateManager.h
dom/interfaces/core/nsIDOMDocument.idl
dom/interfaces/core/nsIDOMElement.idl
dom/interfaces/core/nsIInlineEventHandlers.idl
dom/interfaces/events/nsIDOMMouseEvent.idl
dom/tests/mochitest/Makefile.in
dom/tests/mochitest/pointerlock/Makefile.in
dom/tests/mochitest/pointerlock/file_childIframe.html
dom/tests/mochitest/pointerlock/file_doubleLock.html
dom/tests/mochitest/pointerlock/file_escapeKey.html
dom/tests/mochitest/pointerlock/file_infiniteMovement.html
dom/tests/mochitest/pointerlock/file_locksvgelement.html
dom/tests/mochitest/pointerlock/file_loseFocusWindow.html
dom/tests/mochitest/pointerlock/file_movementXY.html
dom/tests/mochitest/pointerlock/file_nestedFullScreen.html
dom/tests/mochitest/pointerlock/file_pointerLockPref.html
dom/tests/mochitest/pointerlock/file_pointerlock-api.html
dom/tests/mochitest/pointerlock/file_pointerlockerror.html
dom/tests/mochitest/pointerlock/file_removedFromDOM.html
dom/tests/mochitest/pointerlock/file_retargetMouseEvents.html
dom/tests/mochitest/pointerlock/file_screenClientXYConst.html
dom/tests/mochitest/pointerlock/file_suppressSomeMouseEvents.html
dom/tests/mochitest/pointerlock/file_targetOutOfFocus.html
dom/tests/mochitest/pointerlock/file_withoutDOM.html
dom/tests/mochitest/pointerlock/iframe_differentDOM.html
dom/tests/mochitest/pointerlock/pointerlock_utils.js
dom/tests/mochitest/pointerlock/test_pointerlock-api.html
layout/base/nsIPresShell.h
layout/base/nsPresShell.cpp
modules/libpref/src/init/all.js
widget/cocoa/nsChildView.h
widget/gtk2/nsWindow.cpp
widget/gtk2/nsWindow.h
widget/nsGUIEvent.h
widget/nsIWidget.h
widget/windows/nsWindow.h
widget/xpwidgets/nsBaseWidget.h
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -775,16 +775,22 @@ public:
 
   /**
    * Exits all documents from DOM full-screen mode, and moves the top-level
    * browser window out of full-screen mode. If aRunAsync is true, this runs
    * asynchronously.
    */
   static void ExitFullScreen(bool aRunAsync);
 
+
+  virtual void RequestPointerLock(Element* aElement) = 0;
+
+  static void UnlockPointer();
+
+
   //----------------------------------------------------------------------
 
   // Document notification API's
 
   /**
    * Add a new observer of document change notifications. Whenever
    * content is changed, appended, inserted or removed the observers are
    * informed.  An observer that is already observing the document must
--- a/content/base/public/nsINode.h
+++ b/content/base/public/nsINode.h
@@ -1324,16 +1324,18 @@ private:
     NodeIsCCBlackTree,
     // Maybe set if the node is a root of a subtree 
     // which needs to be kept in the purple buffer.
     NodeIsPurpleRoot,
     // Set if the node has an explicit base URI stored
     NodeHasExplicitBaseURI,
     // Set if the element has some style states locked
     ElementHasLockedStyleStates,
+    // Set if element has pointer locked
+    ElementHasPointerLock,
     // Guard value
     BooleanFlagCount
   };
 
   void SetBoolFlag(BooleanFlag name, bool value) {
     PR_STATIC_ASSERT(BooleanFlagCount <= 8*sizeof(mBoolFlags));
     mBoolFlags = (mBoolFlags & ~(1 << name)) | (value << name);
   }
@@ -1382,16 +1384,19 @@ public:
   void SetInCCBlackTree(bool aValue)
     { SetBoolFlag(NodeIsCCBlackTree, aValue); }
   bool InCCBlackTree() const { return GetBoolFlag(NodeIsCCBlackTree); }
   void SetIsPurpleRoot(bool aValue)
     { SetBoolFlag(NodeIsPurpleRoot, aValue); }
   bool IsPurpleRoot() const { return GetBoolFlag(NodeIsPurpleRoot); }
 
   bool HasListenerManager() { return HasFlag(NODE_HAS_LISTENERMANAGER); }
+  bool HasPointerLock() const { return GetBoolFlag(ElementHasPointerLock); }
+  void SetPointerLock() { SetBoolFlag(ElementHasPointerLock); }
+  void ClearPointerLock() { ClearBoolFlag(ElementHasPointerLock); }
 protected:
   void SetParentIsContent(bool aValue) { SetBoolFlag(ParentIsContent, aValue); }
   void SetInDocument() { SetBoolFlag(IsInDocument); }
   void ClearInDocument() { ClearBoolFlag(IsInDocument); }
   void SetIsElement() { SetBoolFlag(NodeIsElement); }
   void ClearIsElement() { ClearBoolFlag(NodeIsElement); }
   void SetHasID() { SetBoolFlag(ElementHasID); }
   void ClearHasID() { ClearBoolFlag(ElementHasID); }
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -8653,16 +8653,23 @@ nsDocument::ExitFullScreen()
 
   // Stores a list of documents to which we must dispatch "mozfullscreenchange".
   // We're required by the spec to dispatch the events in leaf-to-root
   // order when exiting full-screen, but we traverse the doctree in a
   // root-to-leaf order, so we save references to the documents we must
   // dispatch to so that we dispatch in the specified order.
   nsAutoTArray<nsIDocument*, 8> changed;
 
+  // We may also need to unlock the pointer, if it's locked.
+  nsCOMPtr<Element> pointerLockedElement =
+    do_QueryReferent(nsEventStateManager::sPointerLockedElement);
+  if (pointerLockedElement) {
+    UnlockPointer();
+  }
+
   // Walk the tree of full-screen documents, and reset their full-screen state.
   ResetFullScreen(root, static_cast<void*>(&changed));
 
   // Dispatch "mozfullscreenchange" events. Note this loop is in reverse
   // order so that the events for the leaf document arrives before the root
   // document, as required by the spec.
   for (PRUint32 i = 0; i < changed.Length(); ++i) {
     DispatchFullScreenChange(changed[changed.Length() - i - 1]);
@@ -8683,30 +8690,39 @@ nsDocument::RestorePreviousFullScreenSta
 {
   NS_ASSERTION(!IsFullScreenDoc() || sFullScreenDoc != nsnull,
                "Should have a full-screen doc when full-screen!");
 
   if (!IsFullScreenDoc() || !GetWindow() || !sFullScreenDoc) {
     return;
   }
 
+  // If fullscreen mode is updated the pointer should be unlocked
+  nsCOMPtr<Element> pointerLockedElement =
+    do_QueryReferent(nsEventStateManager::sPointerLockedElement);
+  if (pointerLockedElement) {
+    UnlockPointer();
+  }
+
   // Clear full-screen stacks in all descendant documents, bottom up.
   nsCOMPtr<nsIDocument> fullScreenDoc(do_QueryReferent(sFullScreenDoc));
   nsIDocument* doc = fullScreenDoc;
   while (doc != this) {
     NS_ASSERTION(doc->IsFullScreenDoc(), "Should be full-screen doc");
     static_cast<nsDocument*>(doc)->ClearFullScreenStack();
+    UnlockPointer();
     DispatchFullScreenChange(doc);
     doc = doc->GetParentDocument();
   }
 
   // Roll-back full-screen state to previous full-screen element.
   NS_ASSERTION(doc == this, "Must have reached this doc.");
   while (doc != nsnull) {
     static_cast<nsDocument*>(doc)->FullScreenStackPop();
+    UnlockPointer();
     DispatchFullScreenChange(doc);
     if (static_cast<nsDocument*>(doc)->mFullScreenStack.IsEmpty()) {
       // Full-screen stack in document is empty. Go back up to the parent
       // document. We'll pop the containing element off its stack, and use
       // its next full-screen element as the full-screen element.
       doc = doc->GetParentDocument();
     } else {
       // Else we popped the top of the stack, and there's still another
@@ -8972,17 +8988,32 @@ nsDocument::RequestFullScreen(Element* a
   // too. We're required by the spec to dispatch the events in root-to-leaf
   // order, but we traverse the doctree in a leaf-to-root order, so we save
   // references to the documents we must dispatch to so that we get the order
   // as specified.
   nsAutoTArray<nsIDocument*, 8> changed;
 
   // Remember the root document, so that if a full-screen document is hidden
   // we can reset full-screen state in the remaining visible full-screen documents.
-  sFullScreenRootDoc = do_GetWeakReference(nsContentUtils::GetRootDocument(this));
+  nsIDocument* fullScreenDoc = nsContentUtils::GetRootDocument(this);
+  sFullScreenRootDoc = do_GetWeakReference(fullScreenDoc);
+
+  // If a document is already in fullscreen, then unlock the mouse pointer
+  // before setting a new document to fullscreen
+  if (fullScreenDoc) {
+    UnlockPointer();
+  }
+
+  // If a document is already in fullscreen, then unlock the mouse pointer
+  // before setting a new document to fullscreen
+  nsCOMPtr<Element> pointerLockedElement =
+    do_QueryReferent(nsEventStateManager::sPointerLockedElement);
+  if (pointerLockedElement) {
+    UnlockPointer();
+  }
 
   // Set the full-screen element. This sets the full-screen style on the
   // element, and the full-screen-ancestor styles on ancestors of the element
   // in this document.
   DebugOnly<bool> x = FullScreenStackPush(aElement);
   NS_ASSERTION(x, "Full-screen state of requesting doc should always change!");
   changed.AppendElement(this);
   
@@ -9136,16 +9167,233 @@ nsDocument::IsFullScreenEnabled(bool aCa
       return false;
     }
     node = nsContentUtils::GetCrossDocParentNode(node);
   } while (node);
 
   return true;
 }
 
+static void
+DispatchPointerLockChange(nsIDocument* aTarget)
+{
+  nsRefPtr<nsAsyncDOMEvent> e =
+    new nsAsyncDOMEvent(aTarget,
+                        NS_LITERAL_STRING("mozpointerlockchange"),
+                        true,
+                        false);
+  e->PostDOMEvent();
+}
+
+static void
+DispatchPointerLockError(nsIDocument* aTarget)
+{
+  nsRefPtr<nsAsyncDOMEvent> e =
+    new nsAsyncDOMEvent(aTarget,
+                        NS_LITERAL_STRING("mozpointerlockerror"),
+                        true,
+                        false);
+  e->PostDOMEvent();
+}
+
+void
+nsDocument::RequestPointerLock(Element* aElement)
+{
+  NS_ASSERTION(aElement,
+    "Must pass non-null element to nsDocument::RequestPointerLock");
+
+  nsCOMPtr<Element> pointerLockedElement =
+    do_QueryReferent(nsEventStateManager::sPointerLockedElement);
+  if (aElement == pointerLockedElement) {
+    DispatchPointerLockChange(this);
+    return;
+  }
+
+  if (!ShouldLockPointer(aElement) ||
+      !SetPointerLock(aElement, NS_STYLE_CURSOR_NONE)) {
+    DispatchPointerLockError(this);
+    return;
+  }
+
+  aElement->SetPointerLock();
+  nsEventStateManager::sPointerLockedElement = do_GetWeakReference(aElement);
+  nsEventStateManager::sPointerLockedDoc =
+    do_GetWeakReference(static_cast<nsIDocument*>(this));
+  DispatchPointerLockChange(this);
+}
+
+bool
+nsDocument::ShouldLockPointer(Element* aElement)
+{
+  // Check if pointer lock pref is enabled
+  if (!Preferences::GetBool("full-screen-api.pointer-lock.enabled")) {
+    NS_WARNING("ShouldLockPointer(): Pointer Lock pref not enabled");
+    return false;
+  }
+
+  if (aElement != GetFullScreenElement()) {
+    NS_WARNING("ShouldLockPointer(): Element not in fullscreen");
+    return false;
+  }
+
+  if (!aElement->IsInDoc()) {
+    NS_WARNING("ShouldLockPointer(): Element without Document");
+    return false;
+  }
+
+  // Check if the element is in a document with a docshell.
+  nsCOMPtr<nsIDocument> ownerDoc = aElement->OwnerDoc();
+  if (!ownerDoc) {
+    return false;
+  }
+  if (!nsCOMPtr<nsISupports>(ownerDoc->GetContainer())) {
+    return false;
+  }
+  nsCOMPtr<nsPIDOMWindow> ownerWindow = ownerDoc->GetWindow();
+  if (!ownerWindow) {
+    return false;
+  }
+  nsCOMPtr<nsPIDOMWindow> ownerInnerWindow = ownerDoc->GetInnerWindow();
+  if (!ownerInnerWindow) {
+    return false;
+  }
+  if (ownerWindow->GetCurrentInnerWindow() != ownerInnerWindow) {
+    return false;
+  }
+
+  return true;
+}
+
+bool
+nsDocument::SetPointerLock(Element* aElement, int aCursorStyle)
+{
+  // NOTE: aElement will be nsnull when unlocking.
+  nsCOMPtr<nsPIDOMWindow> window = GetWindow();
+  if (!window) {
+    NS_WARNING("SetPointerLock(): No Window");
+    return false;
+  }
+
+  nsIDocShell *docShell = window->GetDocShell();
+  if (!docShell) {
+    NS_WARNING("SetPointerLock(): No DocShell (window already closed?)");
+    return false;
+  }
+
+  nsRefPtr<nsPresContext> presContext;
+  docShell->GetPresContext(getter_AddRefs(presContext));
+  if (!presContext) {
+    NS_WARNING("SetPointerLock(): Unable to get presContext in \
+                domWindow->GetDocShell()->GetPresContext()");
+    return false;
+  }
+
+  nsCOMPtr<nsIPresShell> shell = presContext->PresShell();
+  if (!shell) {
+    NS_WARNING("SetPointerLock(): Unable to find presContext->PresShell()");
+    return false;
+  }
+
+  nsIFrame* rootFrame = shell->GetRootFrame();
+  if (!rootFrame) {
+    NS_WARNING("SetPointerLock(): Unable to get root frame");
+    return false;
+  }
+
+  nsCOMPtr<nsIWidget> widget = rootFrame->GetNearestWidget();
+  if (!widget) {
+    NS_WARNING("SetPointerLock(): Unable to find widget in \
+                shell->GetRootFrame()->GetNearestWidget();");
+    return false;
+  }
+
+  if (aElement && (aElement->OwnerDoc() != this)) {
+    NS_WARNING("SetPointerLock(): Element not in this document.");
+    return false;
+  }
+
+  // Hide the cursor and set pointer lock for future mouse events
+  nsRefPtr<nsEventStateManager> esm = presContext->EventStateManager();
+  esm->SetCursor(aCursorStyle, nsnull, false,
+                 0.0f, 0.0f, widget, true);
+  esm->SetPointerLock(widget, aElement);
+
+  return true;
+}
+
+void
+nsDocument::UnlockPointer()
+{
+  if (!nsEventStateManager::sIsPointerLocked) {
+    return;
+  }
+
+  nsCOMPtr<nsIDocument> pointerLockedDoc =
+    do_QueryReferent(nsEventStateManager::sPointerLockedDoc);
+  if (!pointerLockedDoc) {
+    return;
+  }
+  nsDocument* doc = static_cast<nsDocument*>(pointerLockedDoc.get());
+  if (!doc->SetPointerLock(nsnull, NS_STYLE_CURSOR_AUTO)) {
+    return;
+  }
+
+  nsCOMPtr<Element> pointerLockedElement =
+    do_QueryReferent(nsEventStateManager::sPointerLockedElement);
+  if (!pointerLockedElement) {
+    return;
+  }
+
+  nsEventStateManager::sPointerLockedElement = nsnull;
+  nsEventStateManager::sPointerLockedDoc = nsnull;
+  pointerLockedElement->ClearPointerLock();
+  DispatchPointerLockChange(pointerLockedDoc);
+}
+
+void
+nsIDocument::UnlockPointer()
+{
+  nsDocument::UnlockPointer();
+}
+
+NS_IMETHODIMP
+nsDocument::MozExitPointerLock()
+{
+  UnlockPointer();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocument::GetMozPointerLockElement(nsIDOMElement** aPointerLockedElement)
+{
+  NS_ENSURE_ARG_POINTER(aPointerLockedElement);
+  *aPointerLockedElement = nsnull;
+  nsCOMPtr<Element> pointerLockedElement =
+    do_QueryReferent(nsEventStateManager::sPointerLockedElement);
+  if (!pointerLockedElement) {
+    return NS_OK;
+  }
+
+  // Make sure pointer locked element is in the same document and domain.
+  nsCOMPtr<nsIDocument> pointerLockedDoc =
+    do_QueryReferent(nsEventStateManager::sPointerLockedDoc);
+  nsDocument* doc = static_cast<nsDocument*>(pointerLockedDoc.get());
+  if (doc != this) {
+    return NS_OK;
+  }
+  nsCOMPtr<nsIDOMNode> pointerLockedNode =
+    do_QueryInterface(pointerLockedElement);
+  nsresult rv = nsContentUtils::CheckSameOrigin(this, pointerLockedNode.get());
+  if (NS_FAILED(rv)) {
+    return NS_OK;
+  }
+
+  return CallQueryInterface(pointerLockedElement, aPointerLockedElement);
+}
+
 #define EVENT(name_, id_, type_, struct_)                                 \
   NS_IMETHODIMP nsDocument::GetOn##name_(JSContext *cx, jsval *vp) {      \
     return nsINode::GetOn##name_(cx, vp);                                 \
   }                                                                       \
   NS_IMETHODIMP nsDocument::SetOn##name_(JSContext *cx, const jsval &v) { \
     return nsINode::SetOn##name_(cx, v);                                  \
   }
 #define TOUCH_EVENT EVENT
--- a/content/base/src/nsDocument.h
+++ b/content/base/src/nsDocument.h
@@ -983,16 +983,21 @@ public:
   // Remove the top element from the full-screen stack. Removes the full-screen
   // styles from the former top element, and applies them to the new top
   // element, if there is one.
   void FullScreenStackPop();
 
   // Returns the top element from the full-screen stack.
   Element* FullScreenStackTop();
 
+  void RequestPointerLock(Element* aElement);
+  bool ShouldLockPointer(Element* aElement);
+  bool SetPointerLock(Element* aElement, int aCursorStyle);
+  static void UnlockPointer();
+
   // This method may fire a DOM event; if it does so it will happen
   // synchronously.
   void UpdateVisibilityState();
   // Posts an event to call UpdateVisibilityState
   virtual void PostVisibilityUpdateEvent();
 
   virtual void DocSizeOfExcludingThis(nsWindowSizes* aWindowSizes) const;
   // DocSizeOfIncludingThis is inherited from nsIDocument.
--- a/content/base/src/nsGenericElement.cpp
+++ b/content/base/src/nsGenericElement.cpp
@@ -3269,16 +3269,19 @@ nsGenericElement::UnbindFromTree(bool aD
       // exit full-screen state.
       nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
                                       "DOM", OwnerDoc(),
                                       nsContentUtils::eDOM_PROPERTIES,
                                       "RemovedFullScreenElement");
       // Fully exit full-screen.
       nsIDocument::ExitFullScreen(false);
     }
+    if (HasPointerLock()) {
+      nsIDocument::UnlockPointer();
+    }
     if (GetParent()) {
       NS_RELEASE(mParent);
     } else {
       mParent = nsnull;
     }
     SetParentIsContent(false);
   }
   ClearInDocument();
@@ -6413,17 +6416,25 @@ nsINode::Contains(const nsINode* aOther)
 nsresult
 nsINode::Contains(nsIDOMNode* aOther, bool* aReturn)
 {
   nsCOMPtr<nsINode> node = do_QueryInterface(aOther);
   *aReturn = Contains(node);
   return NS_OK;
 }
 
-nsresult nsGenericElement::MozRequestFullScreen()
+NS_IMETHODIMP
+nsGenericElement::MozRequestPointerLock()
+{
+  OwnerDoc()->RequestPointerLock(this);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGenericElement::MozRequestFullScreen()
 {
   // Only grant full-screen requests if this is called from inside a trusted
   // event handler (i.e. inside an event handler for a user initiated event).
   // This stops the full-screen from being abused similar to the popups of old,
   // and it also makes it harder for bad guys' script to go full-screen and
   // spoof the browser chrome/window and phish logins etc.
   if (!nsContentUtils::IsRequestFullScreenAllowed()) {
     nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -587,16 +587,18 @@ GK_ATOM(monochrome, "monochrome")
 GK_ATOM(mousedown, "mousedown")
 GK_ATOM(mousemove, "mousemove")
 GK_ATOM(mouseout, "mouseout")
 GK_ATOM(mouseover, "mouseover")
 GK_ATOM(mousethrough, "mousethrough")
 GK_ATOM(mouseup, "mouseup")
 GK_ATOM(mozfullscreenchange, "mozfullscreenchange")
 GK_ATOM(mozfullscreenerror, "mozfullscreenerror")
+GK_ATOM(mozpointerlockchange, "mozpointerlockchange")
+GK_ATOM(mozpointerlockerror, "mozpointerlockerror")
 GK_ATOM(moz_opaque, "moz-opaque")
 GK_ATOM(moz_action_hint, "mozactionhint")
 GK_ATOM(x_moz_errormessage, "x-moz-errormessage")
 GK_ATOM(msthemecompatible, "msthemecompatible")
 GK_ATOM(multicol, "multicol")
 GK_ATOM(multiple, "multiple")
 #ifdef MOZ_MEDIA
 GK_ATOM(muted, "muted")
@@ -708,16 +710,18 @@ GK_ATOM(onmouseleave, "onmouseleave")
 GK_ATOM(onmousemove, "onmousemove")
 GK_ATOM(onmouseout, "onmouseout")
 GK_ATOM(onmouseover, "onmouseover")
 GK_ATOM(onMozMouseHittest, "onMozMouseHittest")
 GK_ATOM(onmouseup, "onmouseup")
 GK_ATOM(onMozAfterPaint, "onMozAfterPaint")
 GK_ATOM(onmozfullscreenchange, "onmozfullscreenchange")
 GK_ATOM(onmozfullscreenerror, "onmozfullscreenerror")
+GK_ATOM(onmozpointerlockchange, "onmozpointerlockchange")
+GK_ATOM(onmozpointerlockerror, "onmozpointerlockerror")
 GK_ATOM(onMozMousePixelScroll, "onMozMousePixelScroll")
 GK_ATOM(onMozScrolledAreaChanged, "onMozScrolledAreaChanged")
 GK_ATOM(ononline, "ononline")
 GK_ATOM(onoffline, "onoffline")
 GK_ATOM(onopen, "onopen")
 GK_ATOM(onoverflow, "onoverflow")
 GK_ATOM(onoverflowchanged, "onoverflowchanged")
 GK_ATOM(onpagehide, "onpagehide")
--- a/content/events/public/nsEventNameList.h
+++ b/content/events/public/nsEventNameList.h
@@ -270,16 +270,24 @@ EVENT(mouseup,
 EVENT(mozfullscreenchange,
       NS_FULLSCREENCHANGE,
       EventNameType_HTML,
       NS_EVENT_NULL)
 EVENT(mozfullscreenerror,
       NS_FULLSCREENERROR,
       EventNameType_HTML,
       NS_EVENT_NULL)
+EVENT(mozpointerlockchange,
+      NS_POINTERLOCKCHANGE,
+      EventNameType_HTML,
+      NS_EVENT_NULL)
+EVENT(mozpointerlockerror,
+      NS_POINTERLOCKERROR,
+      EventNameType_HTML,
+      NS_EVENT_NULL)
 // Not supported yet; probably never because "wheel" is a better idea.
 // EVENT(mousewheel)
 EVENT(pause,
       NS_PAUSE,
       EventNameType_HTML,
       NS_EVENT_NULL)
 EVENT(play,
       NS_PLAY,
--- a/content/events/src/nsDOMEvent.cpp
+++ b/content/events/src/nsDOMEvent.cpp
@@ -95,16 +95,18 @@ static const char* const sEventNames[] =
   "loadedmetadata", "loadeddata", "waiting", "playing", "canplay",
   "canplaythrough", "seeking", "seeked", "timeupdate", "ended", "ratechange",
   "durationchange", "volumechange", "MozAudioAvailable",
 #endif // MOZ_MEDIA
   "MozAfterPaint",
   "MozBeforeResize",
   "mozfullscreenchange",
   "mozfullscreenerror",
+  "mozpointerlockchange",
+  "mozpointerlockerror",
   "MozSwipeGesture",
   "MozMagnifyGestureStart",
   "MozMagnifyGestureUpdate",
   "MozMagnifyGesture",
   "MozRotateGestureStart",
   "MozRotateGestureUpdate",
   "MozRotateGesture",
   "MozTapGesture",
@@ -1164,16 +1166,20 @@ nsDOMEvent::Shutdown()
   }
 }
 
 nsIntPoint
 nsDOMEvent::GetScreenCoords(nsPresContext* aPresContext,
                             nsEvent* aEvent,
                             nsIntPoint aPoint)
 {
+  if (nsEventStateManager::sIsPointerLocked) {
+    return nsEventStateManager::sLastScreenPoint;
+  }
+
   if (!aEvent || 
        (aEvent->eventStructType != NS_MOUSE_EVENT &&
         aEvent->eventStructType != NS_POPUP_EVENT &&
         aEvent->eventStructType != NS_MOUSE_SCROLL_EVENT &&
         aEvent->eventStructType != NS_MOZTOUCH_EVENT &&
         aEvent->eventStructType != NS_TOUCH_EVENT &&
         aEvent->eventStructType != NS_DRAG_EVENT &&
         aEvent->eventStructType != NS_SIMPLE_GESTURE_EVENT)) {
@@ -1219,16 +1225,20 @@ nsDOMEvent::GetPageCoords(nsPresContext*
 
 // static
 nsIntPoint
 nsDOMEvent::GetClientCoords(nsPresContext* aPresContext,
                             nsEvent* aEvent,
                             nsIntPoint aPoint,
                             nsIntPoint aDefaultPoint)
 {
+  if (nsEventStateManager::sIsPointerLocked) {
+    return nsEventStateManager::sLastClientPoint;
+  }
+
   if (!aEvent ||
       (aEvent->eventStructType != NS_MOUSE_EVENT &&
        aEvent->eventStructType != NS_POPUP_EVENT &&
        aEvent->eventStructType != NS_MOUSE_SCROLL_EVENT &&
        aEvent->eventStructType != NS_MOZTOUCH_EVENT &&
        aEvent->eventStructType != NS_TOUCH_EVENT &&
        aEvent->eventStructType != NS_DRAG_EVENT &&
        aEvent->eventStructType != NS_SIMPLE_GESTURE_EVENT) ||
--- a/content/events/src/nsDOMEvent.h
+++ b/content/events/src/nsDOMEvent.h
@@ -178,16 +178,18 @@ public:
     eDOMEvents_durationchange,
     eDOMEvents_volumechange,
     eDOMEvents_mozaudioavailable,
 #endif
     eDOMEvents_afterpaint,
     eDOMEvents_beforeresize,
     eDOMEvents_mozfullscreenchange,
     eDOMEvents_mozfullscreenerror,
+    eDOMEvents_mozpointerlockchange,
+    eDOMEvents_mozpointerlockerror,
     eDOMEvents_MozSwipeGesture,
     eDOMEvents_MozMagnifyGestureStart,
     eDOMEvents_MozMagnifyGestureUpdate,
     eDOMEvents_MozMagnifyGesture,
     eDOMEvents_MozRotateGestureStart,
     eDOMEvents_MozRotateGestureUpdate,
     eDOMEvents_MozRotateGesture,
     eDOMEvents_MozTapGesture,
--- a/content/events/src/nsDOMMouseEvent.cpp
+++ b/content/events/src/nsDOMMouseEvent.cpp
@@ -228,16 +228,34 @@ nsDOMMouseEvent::GetRelatedTarget(nsIDOM
       }
     }
 
     CallQueryInterface(relatedTarget, aRelatedTarget);
   }
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsDOMMouseEvent::GetMozMovementX(PRInt32* aMovementX)
+{
+  NS_ENSURE_ARG_POINTER(aMovementX);
+  *aMovementX = GetMovementPoint().x;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMMouseEvent::GetMozMovementY(PRInt32* aMovementY)
+{
+  NS_ENSURE_ARG_POINTER(aMovementY);
+  *aMovementY = GetMovementPoint().y;
+
+  return NS_OK;
+}
+
 NS_METHOD nsDOMMouseEvent::GetScreenX(PRInt32* aScreenX)
 {
   NS_ENSURE_ARG_POINTER(aScreenX);
   *aScreenX = nsDOMEvent::GetScreenCoords(mPresContext,
                                           mEvent,
                                           mEvent->refPoint).x;
   return NS_OK;
 }
--- a/content/events/src/nsDOMUIEvent.cpp
+++ b/content/events/src/nsDOMUIEvent.cpp
@@ -44,25 +44,27 @@
 #include "nsIPresShell.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIDOMWindow.h"
 #include "nsIDOMNode.h"
 #include "nsIContent.h"
 #include "nsContentUtils.h"
 #include "nsEventStateManager.h"
 #include "nsIFrame.h"
-#include "nsLayoutUtils.h"
 #include "nsIScrollableFrame.h"
 #include "DictionaryHelpers.h"
 
 nsDOMUIEvent::nsDOMUIEvent(nsPresContext* aPresContext, nsGUIEvent* aEvent)
   : nsDOMEvent(aPresContext, aEvent ?
                static_cast<nsEvent *>(aEvent) :
                static_cast<nsEvent *>(new nsUIEvent(false, 0, 0)))
   , mClientPoint(0, 0), mLayerPoint(0, 0), mPagePoint(0, 0)
+  , mIsPointerLocked(nsEventStateManager::sIsPointerLocked)
+  , mLastScreenPoint(nsEventStateManager::sLastScreenPoint)
+  , mLastClientPoint(nsEventStateManager::sLastClientPoint)
 {
   if (aEvent) {
     mEventIsInternal = false;
   }
   else {
     mEventIsInternal = true;
     mEvent->time = PR_Now();
   }
@@ -119,65 +121,63 @@ NS_IMPL_RELEASE_INHERITED(nsDOMUIEvent, 
 DOMCI_DATA(UIEvent, nsDOMUIEvent)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMUIEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMUIEvent)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(UIEvent)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEvent)
 
 nsIntPoint
-nsDOMUIEvent::GetScreenPoint()
+nsDOMUIEvent::GetMovementPoint()
 {
-  if (!mEvent || 
+  if (!mEvent ||
        (mEvent->eventStructType != NS_MOUSE_EVENT &&
         mEvent->eventStructType != NS_POPUP_EVENT &&
         mEvent->eventStructType != NS_MOUSE_SCROLL_EVENT &&
         mEvent->eventStructType != NS_MOZTOUCH_EVENT &&
         mEvent->eventStructType != NS_DRAG_EVENT &&
         mEvent->eventStructType != NS_SIMPLE_GESTURE_EVENT)) {
     return nsIntPoint(0, 0);
   }
 
-  if (!((nsGUIEvent*)mEvent)->widget ) {
-    return mEvent->refPoint;
+  if (!((nsGUIEvent*)mEvent)->widget) {
+    return mEvent->lastRefPoint;
   }
 
-  nsIntPoint offset = mEvent->refPoint + 
+  // Calculate the delta between the previous screen point and the current one.
+  nsIntPoint currentPoint = CalculateScreenPoint(mPresContext, mEvent);
+
+  // Adjust previous event's refPoint so it compares to current screenX, screenY
+  nsIntPoint offset = mEvent->lastRefPoint +
     ((nsGUIEvent*)mEvent)->widget->WidgetToScreenOffset();
   nscoord factor = mPresContext->DeviceContext()->UnscaledAppUnitsPerDevPixel();
-  return nsIntPoint(nsPresContext::AppUnitsToIntCSSPixels(offset.x * factor),
-                    nsPresContext::AppUnitsToIntCSSPixels(offset.y * factor));
+  nsIntPoint lastPoint = nsIntPoint(nsPresContext::AppUnitsToIntCSSPixels(offset.x * factor),
+                                    nsPresContext::AppUnitsToIntCSSPixels(offset.y * factor));
+
+  return currentPoint - lastPoint;
+}
+
+nsIntPoint
+nsDOMUIEvent::GetScreenPoint()
+{
+  if (mIsPointerLocked) {
+    return mLastScreenPoint;
+  }
+
+  return CalculateScreenPoint(mPresContext, mEvent);
 }
 
 nsIntPoint
 nsDOMUIEvent::GetClientPoint()
 {
-  if (!mEvent ||
-      (mEvent->eventStructType != NS_MOUSE_EVENT &&
-       mEvent->eventStructType != NS_POPUP_EVENT &&
-       mEvent->eventStructType != NS_MOUSE_SCROLL_EVENT &&
-       mEvent->eventStructType != NS_MOZTOUCH_EVENT &&
-       mEvent->eventStructType != NS_DRAG_EVENT &&
-       mEvent->eventStructType != NS_SIMPLE_GESTURE_EVENT) ||
-      !mPresContext ||
-      !((nsGUIEvent*)mEvent)->widget) {
-    return mClientPoint;
+  if (mIsPointerLocked) {
+    return mLastClientPoint;
   }
 
-  nsPoint pt(0, 0);
-  nsIPresShell* shell = mPresContext->GetPresShell();
-  if (!shell) {
-    return nsIntPoint(0, 0);
-  }
-  nsIFrame* rootFrame = shell->GetRootFrame();
-  if (rootFrame)
-    pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(mEvent, rootFrame);
-
-  return nsIntPoint(nsPresContext::AppUnitsToIntCSSPixels(pt.x),
-                    nsPresContext::AppUnitsToIntCSSPixels(pt.y));
+  return CalculateClientPoint(mPresContext, mEvent, &mClientPoint);
 }
 
 NS_IMETHODIMP
 nsDOMUIEvent::GetView(nsIDOMWindow** aView)
 {
   *aView = mView;
   NS_IF_ADDREF(*aView);
   return NS_OK;
--- a/content/events/src/nsDOMUIEvent.h
+++ b/content/events/src/nsDOMUIEvent.h
@@ -36,16 +36,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsDOMUIEvent_h
 #define nsDOMUIEvent_h
 
 #include "nsIDOMUIEvent.h"
 #include "nsDOMEvent.h"
+#include "nsLayoutUtils.h"
 
 class nsDOMUIEvent : public nsDOMEvent,
                      public nsIDOMUIEvent
 {
 public:
   nsDOMUIEvent(nsPresContext* aPresContext, nsGUIEvent* aEvent);
 
   NS_DECL_ISUPPORTS_INHERITED
@@ -61,20 +62,77 @@ public:
   
   // Forward to nsDOMEvent
   NS_FORWARD_TO_NSDOMEVENT
 
   NS_FORWARD_NSIDOMNSEVENT(nsDOMEvent::)
 
   virtual nsresult InitFromCtor(const nsAString& aType,
                                 JSContext* aCx, jsval* aVal);
+
+  static nsIntPoint CalculateScreenPoint(nsPresContext* aPresContext,
+                                         nsEvent* aEvent)
+  {
+    if (!aEvent ||
+        (aEvent->eventStructType != NS_MOUSE_EVENT &&
+         aEvent->eventStructType != NS_POPUP_EVENT &&
+         aEvent->eventStructType != NS_MOUSE_SCROLL_EVENT &&
+         aEvent->eventStructType != NS_MOZTOUCH_EVENT &&
+         aEvent->eventStructType != NS_DRAG_EVENT &&
+         aEvent->eventStructType != NS_SIMPLE_GESTURE_EVENT)) {
+      return nsIntPoint(0, 0);
+    }
+
+    if (!((nsGUIEvent*)aEvent)->widget ) {
+      return aEvent->refPoint;
+    }
+
+    nsIntPoint offset = aEvent->refPoint +
+                        ((nsGUIEvent*)aEvent)->widget->WidgetToScreenOffset();
+    nscoord factor = aPresContext->DeviceContext()->UnscaledAppUnitsPerDevPixel();
+    return nsIntPoint(nsPresContext::AppUnitsToIntCSSPixels(offset.x * factor),
+                      nsPresContext::AppUnitsToIntCSSPixels(offset.y * factor));
+  }
+
+  static nsIntPoint CalculateClientPoint(nsPresContext* aPresContext,
+                                         nsEvent* aEvent,
+                                         nsIntPoint* aDefaultClientPoint)
+  {
+    if (!aEvent ||
+        (aEvent->eventStructType != NS_MOUSE_EVENT &&
+         aEvent->eventStructType != NS_POPUP_EVENT &&
+         aEvent->eventStructType != NS_MOUSE_SCROLL_EVENT &&
+         aEvent->eventStructType != NS_MOZTOUCH_EVENT &&
+         aEvent->eventStructType != NS_DRAG_EVENT &&
+         aEvent->eventStructType != NS_SIMPLE_GESTURE_EVENT) ||
+        !aPresContext ||
+        !((nsGUIEvent*)aEvent)->widget) {
+      return (nsnull == aDefaultClientPoint ? nsIntPoint(0, 0) :
+        nsIntPoint(aDefaultClientPoint->x, aDefaultClientPoint->y));
+    }
+
+    nsPoint pt(0, 0);
+    nsIPresShell* shell = aPresContext->GetPresShell();
+    if (!shell) {
+      return nsIntPoint(0, 0);
+    }
+    nsIFrame* rootFrame = shell->GetRootFrame();
+    if (rootFrame) {
+      pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, rootFrame);
+    }
+
+    return nsIntPoint(nsPresContext::AppUnitsToIntCSSPixels(pt.x),
+                      nsPresContext::AppUnitsToIntCSSPixels(pt.y));
+  }
+
 protected:
   // Internal helper functions
   nsIntPoint GetScreenPoint();
   nsIntPoint GetClientPoint();
+  nsIntPoint GetMovementPoint();
   nsIntPoint GetLayerPoint();
   nsIntPoint GetPagePoint();
 
   // Allow specializations.
   virtual nsresult Which(PRUint32* aWhich)
   {
     NS_ENSURE_ARG_POINTER(aWhich);
     // Usually we never reach here, as this is reimplemented for mouse and keyboard events.
@@ -83,15 +141,19 @@ protected:
   }
 
   nsCOMPtr<nsIDOMWindow> mView;
   PRInt32 mDetail;
   nsIntPoint mClientPoint;
   // Screenpoint is mEvent->refPoint.
   nsIntPoint mLayerPoint;
   nsIntPoint mPagePoint;
+  nsIntPoint mMovement;
+  bool mIsPointerLocked;
+  nsIntPoint mLastScreenPoint;
+  nsIntPoint mLastClientPoint;
 };
 
 #define NS_FORWARD_TO_NSDOMUIEVENT \
   NS_FORWARD_NSIDOMUIEVENT(nsDOMUIEvent::) \
   NS_FORWARD_TO_NSDOMEVENT
 
 #endif // nsDOMUIEvent_h
--- a/content/events/src/nsEventStateManager.cpp
+++ b/content/events/src/nsEventStateManager.cpp
@@ -132,16 +132,18 @@
 #include "mozilla/Services.h"
 #include "mozAutoDocUpdate.h"
 #include "nsHTMLLabelElement.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/LookAndFeel.h"
 #include "sampler.h"
 
+#include "nsIDOMClientRect.h"
+
 #ifdef XP_MACOSX
 #import <ApplicationServices/ApplicationServices.h>
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 //#define DEBUG_DOCSHELL_FOCUS
@@ -154,16 +156,25 @@ static bool sLeftClickOnly = true;
 static bool sKeyCausesActivation = true;
 static PRUint32 sESMInstanceCount = 0;
 static PRInt32 sChromeAccessModifier = 0, sContentAccessModifier = 0;
 PRInt32 nsEventStateManager::sUserInputEventDepth = 0;
 bool nsEventStateManager::sNormalLMouseEventInProcess = false;
 nsEventStateManager* nsEventStateManager::sActiveESM = nsnull;
 nsIDocument* nsEventStateManager::sMouseOverDocument = nsnull;
 nsWeakFrame nsEventStateManager::sLastDragOverFrame = nsnull;
+nsIntPoint nsEventStateManager::sLastRefPoint = nsIntPoint(0,0);
+nsIntPoint nsEventStateManager::sLastScreenOffset = nsIntPoint(0,0);
+nsIntPoint nsEventStateManager::sLastScreenPoint = nsIntPoint(0,0);
+nsIntPoint nsEventStateManager::sLastClientPoint = nsIntPoint(0,0);
+bool nsEventStateManager::sIsPointerLocked = false;
+// Reference to the pointer locked element.
+nsWeakPtr nsEventStateManager::sPointerLockedElement;
+// Reference to the document which requested pointer lock.
+nsWeakPtr nsEventStateManager::sPointerLockedDoc;
 nsCOMPtr<nsIContent> nsEventStateManager::sDragOverContent = nsnull;
 
 static PRUint32 gMouseOrKeyboardEventCounter = 0;
 static nsITimer* gUserInteractionTimer = nsnull;
 static nsITimerCallback* gUserInteractionTimerCallback = nsnull;
 
 // Pixel scroll accumulation for synthetic line scrolls
 static nscoord gPixelScrollDeltaX = 0;
@@ -767,16 +778,17 @@ nsMouseWheelTransaction::LimitToOnePageS
 }
 
 /******************************************************************/
 /* nsEventStateManager                                            */
 /******************************************************************/
 
 nsEventStateManager::nsEventStateManager()
   : mLockCursor(0),
+    mPreLockPoint(0,0),
     mCurrentTarget(nsnull),
     mLastMouseOverFrame(nsnull),
     // init d&d gesture state machine variables
     mGestureDownPoint(0,0),
     mPresContext(nsnull),
     mLClickCount(0),
     mMClickCount(0),
     mRClickCount(0),
@@ -1041,16 +1053,33 @@ nsEventStateManager::PreHandleEvent(nsPr
   mCurrentTarget = aTargetFrame;
   mCurrentTargetContent = nsnull;
 
   // Focus events don't necessarily need a frame.
   if (NS_EVENT_NEEDS_FRAME(aEvent)) {
     NS_ASSERTION(mCurrentTarget, "mCurrentTarget is null.  this should not happen.  see bug #13007");
     if (!mCurrentTarget) return NS_ERROR_NULL_POINTER;
   }
+#ifdef DEBUG
+  if (NS_IS_DRAG_EVENT(aEvent) && sIsPointerLocked) {
+    NS_ASSERTION(sIsPointerLocked,
+      "sIsPointerLocked is true. Drag events should be suppressed when the pointer is locked.");
+  }
+#endif
+  // Store last known screenPoint and clientPoint so pointer lock
+  // can use these values as constants.
+  if (NS_IS_TRUSTED_EVENT(aEvent) &&
+      (NS_IS_MOUSE_EVENT_STRUCT(aEvent) &&
+       IsMouseEventReal(aEvent)) ||
+       aEvent->eventStructType == NS_MOUSE_SCROLL_EVENT) {
+    if (!sIsPointerLocked) {
+      sLastScreenPoint = nsDOMUIEvent::CalculateScreenPoint(aPresContext, aEvent);
+      sLastClientPoint = nsDOMUIEvent::CalculateClientPoint(aPresContext, aEvent, nsnull);
+    }
+  }
 
   // Do not take account NS_MOUSE_ENTER/EXIT so that loading a page
   // when user is not active doesn't change the state to active.
   if (NS_IS_TRUSTED_EVENT(aEvent) &&
       ((aEvent->eventStructType == NS_MOUSE_EVENT  &&
         IsMouseEventReal(aEvent) &&
         aEvent->message != NS_MOUSE_ENTER &&
         aEvent->message != NS_MOUSE_EXIT) ||
@@ -3774,16 +3803,35 @@ public:
   nsCOMPtr<nsIContent> mTarget;
 };
 
 nsIFrame*
 nsEventStateManager::DispatchMouseEvent(nsGUIEvent* aEvent, PRUint32 aMessage,
                                         nsIContent* aTargetContent,
                                         nsIContent* aRelatedContent)
 {
+  // http://dvcs.w3.org/hg/webevents/raw-file/default/mouse-lock.html#methods
+  // "[When the mouse is locked on an element...e]vents that require the concept
+  // of a mouse cursor must not be dispatched (for example: mouseover, mouseout).
+  if (sIsPointerLocked &&
+      (aMessage == NS_MOUSELEAVE ||
+       aMessage == NS_MOUSEENTER ||
+       aMessage == NS_MOUSE_ENTER_SYNTH ||
+       aMessage == NS_MOUSE_EXIT_SYNTH)) {
+    mCurrentTargetContent = nsnull;
+    nsCOMPtr<Element> pointerLockedElement =
+      do_QueryReferent(nsEventStateManager::sPointerLockedElement);
+    if (!pointerLockedElement) {
+      NS_WARNING("Should have pointer locked element, but didn't.");
+      return nsnull;
+    }
+    nsCOMPtr<nsIContent> content = do_QueryInterface(pointerLockedElement);
+    return mPresContext->GetPrimaryFrameFor(content);
+  }
+
   SAMPLE_LABEL("Input", "DispatchMouseEvent");
   nsEventStatus status = nsEventStatus_eIgnore;
   nsMouseEvent event(NS_IS_TRUSTED_EVENT(aEvent), aMessage, aEvent->widget,
                      nsMouseEvent::eReal);
   event.refPoint = aEvent->refPoint;
   event.isShift = ((nsMouseEvent*)aEvent)->isShift;
   event.isControl = ((nsMouseEvent*)aEvent)->isControl;
   event.isAlt = ((nsMouseEvent*)aEvent)->isAlt;
@@ -3984,16 +4032,36 @@ nsEventStateManager::GenerateMouseEnterE
     return;
 
   // Hold onto old target content through the event and reset after.
   nsCOMPtr<nsIContent> targetBeforeEvent = mCurrentTargetContent;
 
   switch(aEvent->message) {
   case NS_MOUSE_MOVE:
     {
+      if (sIsPointerLocked && aEvent->widget) {
+        // Perform mouse lock by recentering the mouse directly, then remembering the deltas.
+        nsIntRect bounds;
+        aEvent->widget->GetScreenBounds(bounds);
+        aEvent->lastRefPoint = GetMouseCoords(bounds);
+
+        // refPoint should not be the centre on mousemove
+        if (aEvent->refPoint.x == aEvent->lastRefPoint.x &&
+            aEvent->refPoint.y == aEvent->lastRefPoint.y) {
+          aEvent->refPoint = sLastRefPoint;
+        } else {
+          aEvent->widget->SynthesizeNativeMouseMove(aEvent->lastRefPoint);
+        }
+      } else {
+        aEvent->lastRefPoint = nsIntPoint(sLastRefPoint.x, sLastRefPoint.y);
+      }
+
+      // Update the last known refPoint with the current refPoint.
+      sLastRefPoint = nsIntPoint(aEvent->refPoint.x, aEvent->refPoint.y);
+
       // Get the target content target (mousemove target == mouseover target)
       nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aEvent);
       if (!targetElement) {
         // We're always over the document root, even if we're only
         // over dead space in a page (whose frame is not associated with
         // any content) or in print preview dead space
         targetElement = mDocument->GetRootElement();
       }
@@ -4020,16 +4088,89 @@ nsEventStateManager::GenerateMouseEnterE
     break;
   }
 
   // reset mCurretTargetContent to what it was
   mCurrentTargetContent = targetBeforeEvent;
 }
 
 void
+nsEventStateManager::SetPointerLock(nsIWidget* aWidget,
+                                    nsIContent* aElement)
+{
+  // NOTE: aElement will be nsnull when unlocking.
+  sIsPointerLocked = !!aElement;
+
+  if (!aWidget) {
+    return;
+  }
+
+  // Reset mouse wheel transaction
+  nsMouseWheelTransaction::EndTransaction();
+
+  // Deal with DnD events
+  nsCOMPtr<nsIDragService> dragService =
+    do_GetService("@mozilla.org/widget/dragservice;1");
+
+  if (sIsPointerLocked) {
+    // Store the last known ref point so we can reposition the pointer after unlock.
+    mPreLockPoint = sLastRefPoint + sLastScreenOffset;
+
+    nsIntRect bounds;
+    aWidget->GetScreenBounds(bounds);
+    sLastRefPoint = GetMouseCoords(bounds);
+    aWidget->SynthesizeNativeMouseMove(sLastRefPoint);
+
+    // Retarget all events to this element via capture.
+    nsIPresShell::SetCapturingContent(aElement, CAPTURE_POINTERLOCK);
+
+    // Suppress DnD
+    if (dragService) {
+      dragService->Suppress();
+    }
+  } else {
+    // Unlocking, so return pointer to the original position
+    aWidget->SynthesizeNativeMouseMove(sLastScreenPoint);
+
+    // Don't retarget events to this element any more.
+    nsIPresShell::SetCapturingContent(nsnull, CAPTURE_POINTERLOCK);
+
+    // Unsuppress DnD
+    if (dragService) {
+      dragService->Unsuppress();
+    }
+  }
+}
+
+nsIntPoint
+nsEventStateManager::GetMouseCoords(nsIntRect aBounds)
+{
+  NS_ASSERTION(sIsPointerLocked, "GetMouseCoords when not pointer locked!");
+
+  nsCOMPtr<nsIDocument> pointerLockedDoc =
+    do_QueryReferent(nsEventStateManager::sPointerLockedDoc);
+  if (!pointerLockedDoc) {
+    NS_WARNING("GetMouseCoords(): No Document");
+    return nsIntPoint(0, 0);
+  }
+
+  nsCOMPtr<nsPIDOMWindow> domWin = pointerLockedDoc->GetInnerWindow();
+  if (!domWin) {
+    NS_WARNING("GetMouseCoords(): No Window");
+    return nsIntPoint(0, 0);
+  }
+
+  int innerHeight;
+  domWin->GetInnerHeight(&innerHeight);
+
+  return nsIntPoint((aBounds.width / 2) + aBounds.x,
+                    (innerHeight / 2) + (aBounds.y + (aBounds.height - innerHeight)));
+}
+
+void
 nsEventStateManager::GenerateDragDropEnterExit(nsPresContext* aPresContext,
                                                nsGUIEvent* aEvent)
 {
   //Hold onto old target content through the event and reset after.
   nsCOMPtr<nsIContent> targetBeforeEvent = mCurrentTargetContent;
 
   switch(aEvent->message) {
   case NS_DRAGDROP_OVER:
--- a/content/events/src/nsEventStateManager.h
+++ b/content/events/src/nsEventStateManager.h
@@ -223,16 +223,22 @@ public:
   static void SetActiveManager(nsEventStateManager* aNewESM,
                                nsIContent* aContent);
 
   // Sets the full-screen event state on aElement to aIsFullScreen.
   static void SetFullScreenState(mozilla::dom::Element* aElement, bool aIsFullScreen);
 
   static bool IsRemoteTarget(nsIContent* aTarget);
 
+  static nsIntPoint sLastScreenPoint;
+  static nsIntPoint sLastClientPoint;
+  static bool sIsPointerLocked;
+  static nsWeakPtr sPointerLockedElement;
+  static nsWeakPtr sPointerLockedDoc;
+
 protected:
   friend class MouseEnterLeaveDispatcher;
 
   void UpdateCursor(nsPresContext* aPresContext, nsEvent* aEvent, nsIFrame* aTargetFrame, nsEventStatus* aStatus);
   /**
    * Turn a GUI mouse event into a mouse event targeted at the specified
    * content.  This returns the primary frame for the content (or null
    * if it goes away during the event).
@@ -475,21 +481,26 @@ private:
                                    bool aAddState);
   static void UpdateAncestorState(nsIContent* aStartNode,
                                   nsIContent* aStopBefore,
                                   nsEventStates aState,
                                   bool aAddState);
 
   PRInt32     mLockCursor;
 
+  // Point when mouse was locked, used to reposition after unlocking.
+  nsIntPoint  mPreLockPoint;
+
   nsWeakFrame mCurrentTarget;
   nsCOMPtr<nsIContent> mCurrentTargetContent;
   nsWeakFrame mLastMouseOverFrame;
   nsCOMPtr<nsIContent> mLastMouseOverElement;
   static nsWeakFrame sLastDragOverFrame;
+  static nsIntPoint sLastRefPoint;
+  static nsIntPoint sLastScreenOffset;
 
   // member variables for the d&d gesture state machine
   nsIntPoint mGestureDownPoint; // screen coordinates
   // The content to use as target if we start a d&d (what we drag).
   nsCOMPtr<nsIContent> mGestureDownContent;
   // The content of the frame where the mouse-down event occurred. It's the same
   // as the target in most cases but not always - for example when dragging
   // an <area> of an image map this is the image. (bug 289667)
@@ -551,16 +562,19 @@ public:
 
   // Functions used for click hold context menus
   bool mClickHoldContextMenu;
   nsCOMPtr<nsITimer> mClickHoldTimer;
   void CreateClickHoldTimer ( nsPresContext* aPresContext, nsIFrame* inDownFrame,
                               nsGUIEvent* inMouseDownEvent ) ;
   void KillClickHoldTimer ( ) ;
   void FireContextClick ( ) ;
+
+  void SetPointerLock(nsIWidget* aWidget, nsIContent* aElement) ;
+  nsIntPoint GetMouseCoords(nsIntRect aBounds);
   static void sClickHoldCallback ( nsITimer* aTimer, void* aESM ) ;
 };
 
 /**
  * This class is used while processing real user input. During this time, popups
  * are allowed. For mousedown events, mouse capturing is also permitted.
  */
 class nsAutoHandlingUserInputStatePusher
--- a/dom/interfaces/core/nsIDOMDocument.idl
+++ b/dom/interfaces/core/nsIDOMDocument.idl
@@ -61,17 +61,17 @@ interface nsIDOMLocation;
  * cannot exist outside the context of a Document, the nsIDOMDocument 
  * interface also contains the factory methods needed to create these 
  * objects.
  *
  * For more information on this interface please see 
  * http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html
  */
 
-[scriptable, uuid(ac4942fe-1679-4000-aaa7-41dee590a120)]
+[scriptable, uuid(e8d73d0a-cbf6-4037-a565-32c8d64cc294)]
 interface nsIDOMDocument : nsIDOMNode
 {
   readonly attribute nsIDOMDocumentType         doctype;
   readonly attribute nsIDOMDOMImplementation    implementation;
   readonly attribute nsIDOMElement              documentElement;
   nsIDOMElement                 createElement(in DOMString tagName)
                                   raises(DOMException);
   nsIDOMDocumentFragment        createDocumentFragment();
@@ -392,16 +392,31 @@ interface nsIDOMDocument : nsIDOMNode
    * plugins are present, and all ancestor documents have the
    * mozallowfullscreen attribute set.
    *
    * @see <https://wiki.mozilla.org/index.php?title=Gecko:FullScreenAPI>
    */
   readonly attribute boolean mozFullScreenEnabled;
 
   /**
+   * The element to which the mouse pointer is locked, if any, as per the
+   * DOM pointer lock api.
+   *
+   * @see <http://dvcs.w3.org/hg/pointerlock/raw-file/default/index.html>
+   */
+  readonly attribute nsIDOMElement mozPointerLockElement;
+
+  /**
+   * Exit pointer is lock if locked, as per the DOM pointer lock api.
+   *
+   * @see <http://dvcs.w3.org/hg/pointerlock/raw-file/default/index.html>
+   */
+  void mozExitPointerLock();
+
+  /**
    * Inline event handler for readystatechange events.
    */
   [implicit_jscontext] attribute jsval onreadystatechange;
 
   [implicit_jscontext] attribute jsval onmouseenter;
   [implicit_jscontext] attribute jsval onmouseleave;
 
   /**
--- a/dom/interfaces/core/nsIDOMElement.idl
+++ b/dom/interfaces/core/nsIDOMElement.idl
@@ -44,17 +44,17 @@
 /**
  * The nsIDOMElement interface represents an element in an HTML or 
  * XML document. 
  *
  * For more information on this interface please see 
  * http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#interface-element
  */
 
-[scriptable, uuid(295e05d9-9174-48ae-bc59-d7e6a8757726)]
+[scriptable, uuid(432a86dd-c0b8-4d21-8ac6-5ae57d28ea6b)]
 interface nsIDOMElement : nsIDOMNode
 {
   readonly attribute DOMString        tagName;
 
   /**
    * Returns a DOMTokenList object reflecting the class attribute.
    */
   readonly attribute nsIDOMDOMTokenList classList;
@@ -223,9 +223,17 @@ interface nsIDOMElement : nsIDOMNode
   // Mozilla extensions
   /**
    * Requests that this element be made the full-screen element, as per the DOM
    * full-screen api.
    *
    * @see <https://wiki.mozilla.org/index.php?title=Gecko:FullScreenAPI>
    */
   void mozRequestFullScreen();
+
+  /**
+   * Requests that this element be made the pointer-locked element, as per the DOM
+   * pointer lock api.
+   *
+   * @see <http://dvcs.w3.org/hg/pointerlock/raw-file/default/index.html>
+   */
+  void mozRequestPointerLock();
 };
--- a/dom/interfaces/core/nsIInlineEventHandlers.idl
+++ b/dom/interfaces/core/nsIInlineEventHandlers.idl
@@ -77,16 +77,18 @@ interface nsIInlineEventHandlers : nsISu
   [implicit_jscontext] attribute jsval onmousemove;
   [implicit_jscontext] attribute jsval onmouseout;
   [implicit_jscontext] attribute jsval onmouseover;
   [implicit_jscontext] attribute jsval onmouseup;
   // Not supported yet
   // [implicit_jscontext] attribute jsval onmousewheel;
   [implicit_jscontext] attribute jsval onmozfullscreenchange;
   [implicit_jscontext] attribute jsval onmozfullscreenerror;
+  [implicit_jscontext] attribute jsval onmozpointerlockchange;
+  [implicit_jscontext] attribute jsval onmozpointerlockerror;
   [implicit_jscontext] attribute jsval onpause;
   [implicit_jscontext] attribute jsval onplay;
   [implicit_jscontext] attribute jsval onplaying;
   [implicit_jscontext] attribute jsval onprogress;
   [implicit_jscontext] attribute jsval onratechange;
   [implicit_jscontext] attribute jsval onreset;
   [implicit_jscontext] attribute jsval onscroll;
   [implicit_jscontext] attribute jsval onseeked;
--- a/dom/interfaces/events/nsIDOMMouseEvent.idl
+++ b/dom/interfaces/events/nsIDOMMouseEvent.idl
@@ -43,22 +43,25 @@
 /**
  * The nsIDOMMouseEvent interface is the datatype for all mouse events
  * in the Document Object Model.
  *
  * For more information on this interface please see
  * http://www.w3.org/TR/DOM-Level-2-Events/
  */
 
-[scriptable, uuid(7f57aa45-6792-4d8b-ba5b-201533cf0b2f)]
+[scriptable, uuid(53E29996-F851-4032-B896-8AAFBD0BDF25)]
 interface nsIDOMMouseEvent : nsIDOMUIEvent
 {
   readonly attribute long               screenX;
   readonly attribute long               screenY;
 
+  readonly attribute long               mozMovementX;
+  readonly attribute long               mozMovementY;
+
   readonly attribute long               clientX;
   readonly attribute long               clientY;
 
   readonly attribute boolean            ctrlKey;
   readonly attribute boolean            shiftKey;
   readonly attribute boolean            altKey;
   readonly attribute boolean            metaKey;
 
--- a/dom/tests/mochitest/Makefile.in
+++ b/dom/tests/mochitest/Makefile.in
@@ -43,26 +43,27 @@ VPATH	= @srcdir@
 include $(DEPTH)/config/autoconf.mk
 
 DIRS	+= \
 	dom-level0 \
 	dom-level1-core \
 	dom-level2-core \
 	dom-level2-html \
 	ajax \
+	browser-frame \
 	bugs \
 	chrome \
 	general \
 	whatwg \
 	geolocation \
 	localstorage \
 	orientation \
+	pointerlock \
 	sessionstorage \
 	storageevent \
-	browser-frame \
 	$(NULL)
 
 #needs IPC support, also tests do not run successfully in Firefox for now
 #ifneq (mobile,$(MOZ_BUILD_APP))
 #DIRS	+= notification
 #endif
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/Makefile.in
@@ -0,0 +1,39 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEPTH	= ../../../..
+topsrcdir	= @top_srcdir@
+srcdir	= @srcdir@
+VPATH	= @srcdir@
+relativesrcdir	= dom/tests/mochitest/pointerlock
+
+include $(DEPTH)/config/autoconf.mk
+
+include $(topsrcdir)/config/rules.mk
+
+_TEST_FILES	= \
+		test_pointerlock-api.html \
+		pointerlock_utils.js \
+		file_pointerlock-api.html \
+		file_pointerlockerror.html \
+		file_escapeKey.html \
+		file_withoutDOM.html \
+		file_removedFromDOM.html \
+		file_pointerLockPref.html \
+		file_nestedFullScreen.html \
+		file_doubleLock.html \
+		file_loseFocusWindow.html \
+		file_childIframe.html \
+		file_movementXY.html \
+		file_infiniteMovement.html \
+		file_retargetMouseEvents.html \
+		file_targetOutOfFocus.html \
+		file_screenClientXYConst.html \
+		file_suppressSomeMouseEvents.html \
+		file_locksvgelement.html \
+		iframe_differentDOM.html \
+		$(NULL)
+
+libs:: $(_TEST_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/file_childIframe.html
@@ -0,0 +1,143 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=633602
+-->
+<head>
+  <title>Bug 633602 - file_childIframe.html</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
+  </script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="pointerlock_utils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <style>
+    #parent, #childDiv, #iframe, #table, #table td {
+      margin: 0;
+      padding: 0;
+      border: none;
+    }
+    #iframe, #table {
+      background-color: red;
+      width: 100%;
+      height: 100%;
+    }
+    #childDiv, #table td {
+      background-color: blue;
+      width: 50%;
+      height: 50%;
+    }
+  </style>
+</head>
+<body>
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=633602">
+    Mozilla Bug 633602
+  </a>
+
+  <div id="parent">
+    <table id="childTable">
+      <tr>
+        <td>
+          <iframe id="iframe" src="iframe_differentDOM.html" onload="start();">
+          </iframe>
+        </td>
+        <td>
+          <div id="childDiv">
+          </div>
+        </td>
+      </tr>
+    </table>
+  </div>
+
+  <pre id="test">
+    <script type="application/javascript">
+      /*
+       * Test for Bug 633602
+       * Check if pointer is locked when over a child iframe of
+       * the locked element
+       * Check if pointer is being repositioned back to center of
+       * the locked element even when pointer goes over a child ifame
+       */
+
+      SimpleTest.waitForExplicitFinish();
+
+      var parent = document.getElementById("parent")
+        , childDiv = document.getElementById("childDiv")
+        , iframe = document.getElementById("iframe");
+
+      function MozMovementStats() {
+        this.mozMovementX = false;
+        this.mozMovementY = false;
+      }
+
+      var firstMove = new MozMovementStats()
+        , secondMove = new MozMovementStats()
+        , hoverIframe = false;
+
+      function runTests () {
+        ok(hoverIframe, "Pointer should be locked even when pointer " +
+          "hovers over a child iframe");
+        is(firstMove.mozMovementX, secondMove.mozMovementX, "MovementX of first " +
+          "move to childDiv should be equal to movementX of second move " +
+          "to child div");
+        is(firstMove.mozMovementY, secondMove.mozMovementY, "MovementY of first " +
+          "move to childDiv should be equal to movementY of second move " +
+          "to child div");
+      }
+
+      var firstMoveChild = function (e) {
+        firstMove.mozMovementX = e.mozMovementX;
+        firstMove.mozMovementY = e.mozMovementY;
+
+        parent.removeEventListener("mousemove", firstMoveChild);
+        parent.addEventListener("mousemove", moveIframe);
+
+        synthesizeMouseAtCenter(iframe, {type: "mousemove"}, window);
+      };
+
+      var moveIframe = function (e) {
+        hoverIframe = !!document.mozPointerLockElement;
+
+        parent.removeEventListener("mousemove", moveIframe);
+        parent.addEventListener("mousemove", secondMoveChild);
+
+        synthesizeMouseAtCenter(childDiv, {type: "mousemove"}, window);
+      };
+
+      var secondMoveChild = function (e) {
+        secondMove.mozMovementX = e.mozMovementX;
+        secondMove.mozMovementY = e.mozMovementY;
+        parent.removeEventListener("mousemove", secondMoveChild);
+
+        document.mozCancelFullScreen();
+      };
+
+      document.addEventListener("mozpointerlockchange", function () {
+        if (document.mozPointerLockElement === parent) {
+          parent.addEventListener("mousemove", firstMoveChild);
+          synthesizeMouseAtCenter(childDiv, {type: "mousemove"}, window);
+        }
+      }, false);
+
+      document.addEventListener("mozpointerlockerror", function () {
+        document.mozCancelFullScreen();
+      }, false);
+
+      document.addEventListener("mozfullscreenchange", function (e)  {
+        if (document.mozFullScreenElement === parent) {
+          parent.mozRequestPointerLock();
+        }
+        else {
+          runTests();
+          SimpleTest.finish();
+        }
+      }, false);
+
+      function start() {
+        SimpleTest.waitForFocus(function() {
+          parent.mozRequestFullScreen();
+        });
+      }
+    </script>
+  </pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/file_doubleLock.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=633602
+-->
+<head>
+  <title>Bug 633602 - file_doubleLockCallBack.html</title>
+  <script type="text/javascript"
+          src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript"
+          src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="pointerlock_utils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <style type="text/css">
+      #test-element { background-color: #94E01B; width:100px; height:100px; }
+  </style>
+</head>
+<body onload="start();">
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=633602">
+    Mozilla Bug 633602</a>
+  <div id="div"></div>
+  <pre id="test">
+    <script type="text/javascript">
+      /*
+       * Test for Bug 633602
+       * If element requests pointerlock on itself while in pointerlock state
+       * mozpointerlockchange event should be dispatched
+       */
+
+      SimpleTest.waitForExplicitFinish();
+
+      var div = document.getElementById("div")
+        , numberOfLocks = 0;
+
+      function runTests () {
+        is(numberOfLocks, 2, "Requesting pointer lock on a locked element " +
+          "should dispatch mozpointerlockchange event");
+      }
+
+      document.addEventListener("mozpointerlockchange", function (e) {
+        if (document.mozPointerLockElement === div) {
+          if (numberOfLocks === 2) {
+            document.mozCancelFullScreen();
+          }
+          else {
+            numberOfLocks++;
+            div.mozRequestPointerLock();
+          }
+        }
+      }, false);
+
+      document.addEventListener("mozfullscreenchange", function (e) {
+        if (document.mozFullScreenElement === div) {
+          div.mozRequestPointerLock();
+        }
+        else {
+          runTests();
+          SimpleTest.finish();
+        }
+      }, false);
+
+      function start() {
+        SimpleTest.waitForFocus(function() {
+          div.mozRequestFullScreen();
+        });
+      }
+    </script>
+  </pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/file_escapeKey.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<!--https://bugzilla.mozilla.org/show_bug.cgi?id=633602-->
+<head>
+  <title>Bug 633602</title>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js">
+  </script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
+  </script>
+  <script type="application/javascript" src="pointerlock_utils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="start();">
+  <a target="_blank"
+    href="https://bugzilla.mozilla.org/show_bug.cgi?id=633602">
+    Mozilla Bug 633602
+  </a>
+  <div id="div"></div>
+  <pre id="test">
+    <script type="text/javascript">
+      /*
+       * Test for Bug 633602
+       * Escape key should unlock the pointer
+       */
+
+      SimpleTest.waitForExplicitFinish();
+
+      var div = document.getElementById("div")
+        , pointerUnLocked = false;
+
+      function runTests () {
+        ok(pointerUnLocked, "Pressing Escape key should unlock the pointer");
+      }
+
+      document.addEventListener("mozpointerlockchange", function (e) {
+        if (document.mozPointerLockElement === div) {
+          synthesizeKey("VK_ESCAPE", {});
+        }
+        else {
+          pointerUnLocked = true;
+          document.mozCancelFullScreen();
+        }
+      }, false);
+
+      document.addEventListener("mozfullscreenchange", function(e) {
+        if (document.mozFullScreenElement === div) {
+          div.mozRequestPointerLock();
+        }
+        else {
+          runTests();
+          SimpleTest.finish();
+        }
+      }, false);
+
+      function start() {
+        SimpleTest.waitForFocus(function() {
+          div.mozRequestFullScreen();
+        });
+      }
+    </script>
+  </pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/file_infiniteMovement.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=633602
+-->
+  <head>
+    <title>Bug 633602 - file_movementXY.html</title>
+    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
+    </script>
+    <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js">
+    </script>
+    <script type="application/javascript" src="pointerlock_utils.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  </head>
+  <body onload="start();">
+    <a target="_blank"
+      href="https://bugzilla.mozilla.org/show_bug.cgi?id=633602">
+      Mozilla Bug 633602
+    </a>
+    <div id="div"></div>
+    <pre id="test">
+      <script type="application/javascript">
+        /*
+         * Test for Bug 633602
+         * This test checks if mozMovementX and mozMovementY
+         * are present in a mouse event object.
+         * It also checks the values for mozMovementXY.
+         * They should be equal to the current screenXY minus
+         * the last screenXY
+         * This test will also test that the incremental movement is
+         * not constrained to the width of the screen.
+         */
+
+        SimpleTest.waitForExplicitFinish();
+
+        var div = document.getElementById("div")
+          , divCenterWidth = 0
+          , divCenterHeight = 0
+          , totalMovementX = 0
+          , totalMovementY = 0;
+
+        function runTests () {
+          ok(totalMovementX > div.getBoundingClientRect().width,
+               "Should have moved more than one screen's worth in width." +
+               "TotalX: " + totalMovementX + " Screensize X: " + div.getBoundingClientRect().width);
+          ok(totalMovementY > div.getBoundingClientRect().height,
+             "Should have moved more than one screen's worth in height." +
+             "TotalY: " + totalMovementY + " Screensize Y: " + div.getBoundingClientRect().height);
+        }
+
+        var firstMoveListener = function (e) {
+          div.removeEventListener("mousemove", firstMoveListener, false);
+          div.addEventListener("mousemove", secondMoveListener, false);
+
+          synthesizeMouse(div,(divCenterWidth/2) * 3,
+            (divCenterHeight/2) * 3, {
+            type: "mousemove"
+          }, window);
+        }
+
+        var secondMoveListener = function (e) {
+          totalMovementX = divCenterWidth + ((divCenterWidth / 2) * 3);
+          totalMovementY = divCenterHeight + ((divCenterHeight / 2) * 3);
+
+          div.removeEventListener("mousemove", secondMoveListener, false);
+          document.mozCancelFullScreen();
+        }
+
+        document.addEventListener("mozpointerlockchange", function (e) {
+          if (document.mozPointerLockElement === div) {
+            div.addEventListener("mousemove", firstMoveListener, false);
+
+            divCenterWidth = Math.round(div.getBoundingClientRect().width / 2);
+            divCenterHeight = Math.round(div.getBoundingClientRect().height / 2);
+
+            synthesizeMouse(div, divCenterWidth, divCenterHeight, {
+              type: "mousemove"
+            }, window);
+          }
+        }, false);
+
+        document.addEventListener("mozfullscreenchange", function() {
+          if (document.mozFullScreenElement === div) {
+            div.mozRequestPointerLock();
+          }
+          else {
+            runTests();
+            SimpleTest.finish();
+          }
+        }, false);
+
+        function start() {
+          SimpleTest.waitForFocus(function() {
+            div.mozRequestFullScreen();
+          });
+        }
+      </script>
+    </pre>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/file_locksvgelement.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+  <!--
+  https://bugzilla.mozilla.org/show_bug.cgi?id=633602
+  -->
+  <head>
+    <title>Bug 633602</title>
+    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
+    </script>
+    <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js">
+    </script>
+    <script type="application/javascript" src="pointerlock_utils.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  </head>
+  <body onload="start();">
+    <a target="_blank"
+       href="https://bugzilla.mozilla.org/show_bug.cgi?id=633602">
+      Mozilla Bug 633602</a>
+    <p id="display"></p>
+
+    <svg width="100" height="100" viewbox="0 0 100 100">
+        <rect id="svg-elem" x="10" y="10" width="50" height="50"
+        fill="black" stroke="blue" stroke-width="2"/>
+    </svg>
+
+    <pre id="test">
+      <script type="application/javascript">
+        /*
+         * Test for Bug 633602
+         * Test locking non-html element.
+         */
+
+        SimpleTest.waitForExplicitFinish(1);
+
+        var elem,
+          elemWasLocked = false;
+
+        document.addEventListener("mozpointerlockchange", function (e) {
+          if (document.mozFullScreen &&
+              document.mozPointerLockElement === elem) {
+            elemWasLocked = true;
+            document.mozExitPointerLock();
+          } else {
+            document.mozCancelFullScreen();
+          }
+        }, false);
+
+        document.addEventListener("mozfullscreenchange", function (e) {
+          if (document.mozFullScreenElement === elem) {
+            elem.mozRequestPointerLock();
+          } else {
+            ok(elemWasLocked, "Expected SVG elem to become locked.");
+            SimpleTest.finish();
+          }
+        }, false);
+
+        function start() {
+          elem = document.getElementById("svg-elem");
+
+          SimpleTest.waitForFocus(function() {
+            elem.mozRequestFullScreen();
+          });
+        }
+      </script>
+    </pre>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/file_loseFocusWindow.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=633602
+-->
+<head>
+  <title>Bug 633602 - file_exitMouseLockOnLoseFocus.html</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
+  </script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js">
+  </script>
+  <script type="application/javascript" src="pointerlock_utils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="start();">
+  <a target="_blank"
+    href="https://bugzilla.mozilla.org/show_bug.cgi?id=633602">
+    Mozilla Bug 633602
+  </a>
+  <div id="div"></div>
+  <pre id="test">
+    <script type="text/javascript">
+      /*
+       * Test for Bug 633602
+       * If element has the pointer locked and window loses focus
+       * pointer should be unlocked
+       */
+
+      SimpleTest.waitForExplicitFinish();
+
+      var div = document.getElementById("div")
+        , newWindow = null
+        , looseFocusUnlock = false
+        , pointerLockLost = false;
+
+      function runTests () {
+        ok(looseFocusUnlock, "Pointer should be unlocked if window " +
+            "loses focus");
+      }
+
+      document.addEventListener("mozpointerlockchange", function (e) {
+        if (document.mozPointerLockElement === div) {
+          divPointerLocked = true;
+          newWindow = window.open(); // Open new window to blur current one
+        }
+        else {
+          looseFocusUnlock = true;
+          newWindow && newWindow.close();
+          document.mozCancelFullScreen();
+        }
+      }, false);
+
+      document.addEventListener("mozfullscreenchange", function() {
+        if (document.mozFullScreenElement === div) {
+          div.mozRequestPointerLock();
+        }
+        else {
+          runTests();
+          SimpleTest.finish();
+        }
+      }, false);
+
+      function start() {
+        SimpleTest.waitForFocus(function() {
+          div.mozRequestFullScreen();
+        });
+      }
+    </script>
+  </pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/file_movementXY.html
@@ -0,0 +1,105 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=633602
+-->
+  <head>
+    <title>Bug 633602 - file_movementXY.html</title>
+    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
+    </script>
+    <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js">
+    </script>
+    <script type="application/javascript" src="pointerlock_utils.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  </head>
+  <body onload="start();">
+    <a target="_blank"
+      href="https://bugzilla.mozilla.org/show_bug.cgi?id=633602">
+      Mozilla Bug 633602
+    </a>
+    <div id="div"></div>
+    <pre id="test">
+      <script type="application/javascript">
+        /*
+         * Test for Bug 633602
+         * Checks if mozMovementX and mozMovementY are present
+         * in the mouse event object.
+         * It also checks the values for mozMovementXY.
+         * They should be equal to the current screenXY minus
+         * the last screenXY
+         */
+
+        SimpleTest.waitForExplicitFinish();
+
+        function MouseMovementStats() {
+          this.screenX = false;
+          this.screenY = false;
+          this.mozMovementX = false;
+          this.mozMovementY = false;
+        }
+
+        var div = document.getElementById("div")
+          , divCenterWidth = 0
+          , divCenterHeight = 0
+          , mozMovementX = false
+          , mozMovementY = false
+          , firstMove = new MouseMovementStats()
+          , secondMove = new MouseMovementStats();
+
+        function runTests () {
+          ok(mozMovementX && mozMovementY, "mozMovementX and " +
+            "mozMovementY should exist in mouse events objects.");
+          is(secondMove.mozMovementX, secondMove.screenX - firstMove.screenX,
+           "mozMovementX should be equal to eNow.screenX-ePrevious.screenX");
+          is(secondMove.mozMovementY, secondMove.screenY - firstMove.screenY,
+           "mozMovementY should be equal to eNow.screenY-ePrevious.screenY");
+        }
+
+        var moveMouse = function(e) {
+          mozMovementX = ("mozMovementX" in e);
+          mozMovementY = ("mozMovementY" in e);
+
+          div.removeEventListener("mousemove", moveMouse, false);
+          div.addEventListener("mousemove", moveMouseAgain, false);
+
+          firstMove.screenX = e.screenX;
+          firstMove.screenY = e.screenY;
+
+          divCenterWidth = Math.round(div.getBoundingClientRect().width / 2);
+          divCenterHeight = Math.round(div.getBoundingClientRect().height / 2);
+
+          synthesizeMouse(div, (divCenterWidth + 10), (divCenterHeight + 10), {
+            type: "mousemove"
+          }, window);
+        };
+
+        var moveMouseAgain = function(e) {
+          secondMove.screenX = e.screenX;
+          secondMove.screenY = e.screenY;
+          secondMove.mozMovementX = e.mozMovementX;
+          secondMove.mozMovementY = e.mozMovementY;
+
+          div.removeEventListener("mousemove", moveMouseAgain, false);
+          document.mozCancelFullScreen();
+        };
+
+        document.addEventListener("mozfullscreenchange", function() {
+          if (document.mozFullScreenElement === div) {
+            div.addEventListener("mousemove", moveMouse, false);
+            synthesizeMouseAtCenter(div, {type: "mousemove"}, window);
+          }
+          else {
+            runTests();
+            SimpleTest.finish();
+          }
+        }, false);
+
+        function start() {
+          SimpleTest.waitForFocus(function() {
+            div.mozRequestFullScreen();
+          });
+        }
+      </script>
+    </pre>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/file_nestedFullScreen.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+  <!--
+  https://bugzilla.mozilla.org/show_bug.cgi?id=633602
+  -->
+  <head>
+    <title>Bug 633602 - file_nestedFullScreen.html</title>
+    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
+    </script>
+    <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js">
+    </script>
+    <script type="application/javascript" src="pointerlock_utils.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  </head>
+  <body onload="start();">
+    <a target="_blank"
+       href="https://bugzilla.mozilla.org/show_bug.cgi?id=633602">
+      Mozilla Bug 633602
+    </a>
+
+    <div id="parentDiv">
+      <div id="childDiv"></div>
+    </div>
+
+    <script type="application/javascript">
+      /*
+       * Test for Bug 633602
+       * Requesting fullscreen on a child element of the element with
+       * the pointer locked should unlock the pointer
+       */
+
+      SimpleTest.waitForExplicitFinish();
+
+      var parentDiv = document.getElementById("parentDiv")
+        , childDiv = document.getElementById("childDiv")
+        , parentDivLocked = false
+        , parentDivFullScreen = false
+        , pointerLocked = false;
+
+      function runTests () {
+        ok(parentDivLocked, "After requesting pointerlock on parentDiv " +
+          "document.mozPointerLockElement should be equal to " +
+          " parentDiv element");
+        isnot(pointerLocked, true, "Requesting fullscreen on " +
+          "childDiv while parentDiv still in fullscreen should " +
+          "unlock the pointer");
+      }
+
+      document.addEventListener("mozpointerlockchange", function (e) {
+        if (document.mozPointerLockElement === parentDiv) {
+          parentDivLocked = true;
+          childDiv.mozRequestFullScreen();
+        }
+      }, false);
+
+      document.addEventListener("mozfullscreenchange", function() {
+        if (document.mozFullScreenElement === parentDiv) {
+          if (parentDivFullScreen === true) {
+            document.mozCancelFullScreen();
+          }
+          parentDivFullScreen = true;
+          parentDiv.mozRequestPointerLock();
+        }
+        else if (document.mozFullScreenElement === childDiv) {
+          pointerLocked = !!document.mozPointerLockElement;
+          document.mozCancelFullScreen();
+        }
+        else {
+          runTests();
+          SimpleTest.finish();
+        }
+      }, false);
+
+      function start() {
+        SimpleTest.waitForFocus(function() {
+          parentDiv.mozRequestFullScreen();
+        });
+      }
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/file_pointerLockPref.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+  <!--
+  https://bugzilla.mozilla.org/show_bug.cgi?id=633602
+  -->
+  <head>
+    <title>Bug 633602</title>
+    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
+    </script>
+    <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js">
+    </script>
+    <script type="application/javascript" src="pointerlock_utils.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  </head>
+  <body onload="start();">
+    <a target="_blank"
+       href="https://bugzilla.mozilla.org/show_bug.cgi?id=633602">
+      Mozilla Bug 633602</a>
+    <p id="display"></p>
+    <div id="div"></div>
+    <pre id="test">
+      <script type="application/javascript">
+        /*
+         * Test for Bug 633602
+         * Tests full-screen-api.pointer-lock pref
+         */
+
+        SimpleTest.waitForExplicitFinish();
+
+        var div = document.getElementById("div")
+          , prefEnabled = false
+          , prefDisabled = false;
+
+        function runTests () {
+          ok(prefEnabled, "Element should be able to lock the pointer " +
+            "if pointer-lock pref is set to TRUE");
+          ok(prefDisabled, "Element should NOT be able to lock the pointer " +
+            "if pointer-lock pref is set to FALSE");
+        }
+
+        document.addEventListener("mozpointerlockchange", function (e) {
+          if (document.mozPointerLockElement === div) {
+            prefEnabled = true;
+            document.mozExitPointerLock();
+          }
+          else {
+            SpecialPowers.setBoolPref("full-screen-api.pointer-lock.enabled",
+                                      false );
+            div.mozRequestPointerLock();
+          }
+        }, false);
+
+        document.addEventListener("mozpointerlockerror", function (e) {
+          prefDisabled = true;
+          document.mozCancelFullScreen();
+        }, false);
+
+        document.addEventListener("mozfullscreenchange", function (e) {
+          if (document.mozFullScreenElement === div) {
+            SpecialPowers.setBoolPref("full-screen-api.pointer-lock.enabled",
+                                      true );
+            div.mozRequestPointerLock();
+          }
+          else {
+            SpecialPowers.setBoolPref("full-screen-api.pointer-lock.enabled",
+                                       true );
+            runTests();
+            SimpleTest.finish();
+          }
+        }, false);
+
+        function start() {
+          SimpleTest.waitForFocus(function() {
+            div.mozRequestFullScreen();
+          });
+        }
+      </script>
+    </pre>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/file_pointerlock-api.html
@@ -0,0 +1,92 @@
+<!DOCTYPE HTML>
+<html>
+<!--https://bugzilla.mozilla.org/show_bug.cgi?id=633602-->
+<head>
+  <title>Bug 633602</title>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js">
+  </script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
+  </script>
+  <script type="application/javascript" src="pointerlock_utils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="start();">
+  <a target="_blank"
+    href="https://bugzilla.mozilla.org/show_bug.cgi?id=633602">
+    Mozilla Bug 633602
+  </a>
+  <div id="div"></div>
+  <pre id="test">
+    <script type="text/javascript">
+      /*
+       * Test for Bug 633602
+       * Make sure DOM API is correct.
+       */
+
+      SimpleTest.waitForExplicitFinish();
+
+      var div,
+        hasRequestPointerLock = false,
+        pointerLockChangeEventFired = false,
+        pointerUnlocked = false,
+        pointerLocked = false,
+        hasExitPointerLock = false,
+        pointerLockElement = false,
+        hasMovementX = false,
+        hasMovementY = false;
+
+      function runTests () {
+        ok(hasRequestPointerLock, "Element should have mozRequestPointerLock.");
+        ok(pointerLockChangeEventFired, "pointerlockchange event should fire.");
+        ok(pointerUnlocked, "Should be able to unlock pointer locked element.");
+        ok(pointerLocked, "Requested element should be able to lock.");
+        ok(hasExitPointerLock, "Document should have mozExitPointerLock");
+        ok(pointerLockElement, "Document should keep track of correct pointer locked element");
+        ok(hasMovementX, "Mouse Event should have mozMovementX.");
+        ok(hasMovementY, "Mouse Event should have mozMovementY.");
+      }
+
+      function mouseMoveHandler(e) {
+        document.removeEventListener("mousemove", mouseMoveHandler, false);
+
+        hasMovementX = "mozMovementX" in e;
+        hasMovementY = "mozMovementY" in e;
+
+        hasExitPointerLock = "mozExitPointerLock" in document;
+        document.mozExitPointerLock();
+      }
+
+      document.addEventListener("mozpointerlockchange", function (e) {
+        pointerLockChangeEventFired = true;
+
+        if (document.mozPointerLockElement) {
+          pointerLocked = true;
+          pointerLockElement = document.mozPointerLockElement === div;
+          document.addEventListener("mousemove", mouseMoveHandler, false);
+          synthesizeMouseAtCenter(div, {type: "mousemove"}, window);
+        } else {
+          pointerUnlocked = true;
+          document.mozCancelFullScreen();
+        }
+      }, false);
+
+      document.addEventListener("mozfullscreenchange", function(e) {
+        if (document.mozFullScreenElement === div) {
+          hasRequestPointerLock = "mozRequestPointerLock" in div;
+          div.mozRequestPointerLock();
+        } else {
+          runTests();
+          SimpleTest.finish();
+        }
+      }, false);
+
+      function start() {
+        SimpleTest.waitForFocus(function() {
+          div = document.getElementById("div");
+          div.mozRequestFullScreen();
+        });
+      }
+    </script>
+  </pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/file_pointerlockerror.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--https://bugzilla.mozilla.org/show_bug.cgi?id=633602-->
+<head>
+  <title>Bug 633602</title>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js">
+  </script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
+  </script>
+  <script type="application/javascript" src="pointerlock_utils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="start();">
+  <a target="_blank"
+    href="https://bugzilla.mozilla.org/show_bug.cgi?id=633602">
+    Mozilla Bug 633602
+  </a>
+
+  <pre id="test">
+    <script type="text/javascript">
+      /*
+       * Test for Bug 633602
+       * Make sure pointerlockerror event fires.
+       */
+
+      SimpleTest.waitForExplicitFinish();
+
+      document.addEventListener("mozpointerlockerror", function (e) {
+        ok(true, "pointerlockerror event should fire.");
+        SimpleTest.finish();
+      }, false);
+
+      function start() {
+        SimpleTest.waitForFocus(function() {
+          // element not in the DOM, not fullscreen, should fail to lock
+          div = document.createElement("div");
+          div.mozRequestPointerLock();
+        });
+      }
+    </script>
+  </pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/file_removedFromDOM.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+  <!--
+  https://bugzilla.mozilla.org/show_bug.cgi?id=633602
+
+  Test DOM tree in full screen
+  -->
+  <head>
+    <title>Bug 633602 - file_DOMtree.html</title>
+    <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js">
+    </script>
+    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
+    </script>
+    <script type="application/javascript" src="pointerlock_utils.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+    <style>
+    </style>
+  </head>
+  <body onload="start();">
+    <a target="_blank"
+       href="https://bugzilla.mozilla.org/show_bug.cgi?id=633602">
+      Mozilla Bug 633602
+    </a>
+    <div id="div"></div>
+    <pre id="test">
+      <script type="text/javascript">
+        /*
+         * Test for Bug 633602
+         * Checks if pointer is unlocked when element is removed from
+         * the DOM Tree
+         */
+
+        SimpleTest.waitForExplicitFinish();
+
+        var div = document.getElementById("div")
+          , removedDOM = false;
+
+        function runTests() {
+          ok(removedDOM, "Pointer should be unlocked when " +
+            "an element is removed the DOM Tree");
+        }
+
+        document.addEventListener("mozpointerlockchange", function (e) {
+          if (document.mozPointerLockElement === div) {
+            document.body.removeChild(div);
+            removedDOM = !document.mozPointerLockElement;
+            document.mozCancelFullScreen();
+          }
+
+        }, false);
+
+        document.addEventListener("mozfullscreenchange", function (e) {
+          if (document.mozFullScreenElement === div) {
+              div.mozRequestPointerLock();
+          }
+          else {
+            runTests();
+            SimpleTest.finish();
+          }
+        }, false);
+
+        function start() {
+          SimpleTest.waitForFocus(function() {
+            div.mozRequestFullScreen();
+          });
+        }
+      </script>
+    </pre>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/file_retargetMouseEvents.html
@@ -0,0 +1,164 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=633602
+-->
+<head>
+  <title>Bug 633602 - file_retargetMouseEvents.html</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
+  </script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="pointerlock_utils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="start();">
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=633602">
+    Mozilla Bug 633602
+  </a>
+
+  <div id="parent">
+    <div id="child" style="width: 100%; height: 100%;">
+    </div>
+  </div>
+
+  <pre id="test">
+    <script type="application/javascript">
+      /*
+       * Test for Bug 633602
+       * Retarget mouse events to the locked element
+       */
+
+      SimpleTest.waitForExplicitFinish();
+
+      function MouseEventStats() {
+        this.mouseMove = false;
+        this.mouseDown = false;
+        this.mouseUp = false;
+        this.mouseClick = false;
+        this.mouseScroll = false;
+      }
+
+      var parent = document.getElementById("parent")
+        , child = document.getElementById("child")
+        , parentStats = new MouseEventStats()
+        , childStats = new MouseEventStats();
+
+      function runTests () {
+        is(childStats.mouseMove, false, "Child shound not receive mousemove event.");
+        is(childStats.mouseDown, false, "Child should not receive mousedown event.");
+        is(childStats.mouseUp, false, "Child should not receive mouseup event.");
+        is(childStats.mouseClick, false, "Child should not receive click event.");
+        is(childStats.mouseScroll, false, "Child should not receive DOMMouseScroll event.");
+
+        ok(parentStats.mouseMove, "Parent should receive mousemove event.");
+        ok(parentStats.mouseDown, "Parent should receive mousedown event.");
+        ok(parentStats.mouseUp, "Parent should receive mouseup event.");
+        ok(parentStats.mouseClick, "Parent should receive click event.");
+        ok(parentStats.mouseScroll, "Parent should receive DOMMouseScroll event.");
+      }
+
+
+      /**
+       * The event listeners for the child element shouldn't be fired
+       * Mouse events will only happen when the pointer is locked
+       * and if the pointer is locked all the mouse events should be
+       * retargetted to the locked element
+       **/
+      var childMoveTest = function() {
+        childStats.mouseMove = true;
+      }
+
+      var childDownTest = function() {
+        childStats.mouseDown = true;
+      };
+
+      var childUpTest = function() {
+        childStats.mouseUp = true;
+      };
+
+      var childClickTest = function() {
+        childStats.mouseClick = true;
+      };
+
+      var childScrollTest = function() {
+        childStats.mouseScroll = true;
+      };
+
+      //  Event listeners for the parent element
+      var startMouseTests = function() {
+        parent.removeEventListener("mousemove", startMouseTests);
+        parent.addEventListener("DOMMouseScroll", parentScrollTest);
+        child.addEventListener("DOMMouseScroll", childScrollTest);
+        synthesizeMouseScroll(child, 5, 5, {'delta': 10, 'type': "DOMMouseScroll"});
+      };
+
+      var parentScrollTest = function (e) {
+        parentStats.mouseScroll = true;
+        parent.removeEventListener("DOMMouseScroll", parentScrollTest);
+        child.removeEventListener("DOMMouseScroll", childScrollTest);
+        parent.addEventListener("mousedown", parentDownTest);
+        child.addEventListener("mousedown", childDownTest);
+        synthesizeMouseAtCenter(child, {type: "mousedown"}, window);
+      };
+
+      var parentDownTest = function (e) {
+        parentStats.mouseDown = true;
+        parent.removeEventListener("mousedown", parentDownTest);
+        child.removeEventListener("mousedown", childDownTest);
+        parent.addEventListener("mouseup", parentUpTest);
+        child.addEventListener("mouseup", childUpTest);
+        synthesizeMouseAtCenter(child, {type: "mouseup"}, window);
+      };
+
+      var parentUpTest = function (e) {
+        parentStats.mouseUp = true;
+        parent.removeEventListener("mouseup", parentUpTest);
+        child.removeEventListener("mouseup", childUpTest);
+        parent.addEventListener("click", parentClickTest);
+        child.addEventListener("click", childClickTest);
+        synthesizeMouseAtCenter(child, {type: "click"}, window);
+      };
+
+      var parentClickTest = function (e) {
+        parentStats.mouseClick = true;
+        parent.removeEventListener("click", parentClickTest);
+        child.removeEventListener("click", childClickTest);
+        parent.addEventListener("mousemove", parentMoveTest);
+        child.addEventListener("mousemove", childMoveTest);
+        synthesizeMouseAtCenter(child, {type: "mousemove"}, window);
+      };
+
+      var parentMoveTest = function (e) {
+        parentStats.mouseMove = true;
+        parent.removeEventListener("mousemove", parentMoveTest);
+        child.removeEventListener("mousemove", childMoveTest);
+        document.mozCancelFullScreen();
+      }
+
+      document.addEventListener("mozpointerlockchange", function (e) {
+        if (document.mozPointerLockElement === parent) {
+          parent.addEventListener("mousemove", startMouseTests);
+          child.addEventListener("mousemove", childMoveTest);
+          synthesizeMouseAtCenter(parent, {type: "mousemove"}, window);
+        }
+      }, false);
+
+      document.addEventListener("mozfullscreenchange", function (e)  {
+        if (document.mozFullScreenElement === parent) {
+          parent.mozRequestPointerLock();
+        }
+        else {
+          runTests();
+          SimpleTest.finish();
+        }
+      }, false);
+
+      function start() {
+        SimpleTest.waitForFocus(function() {
+          parent.mozRequestFullScreen();
+        });
+      }
+    </script>
+  </pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/file_screenClientXYConst.html
@@ -0,0 +1,106 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=633602
+-->
+<head>
+  <title>Bug 633602 - constantXY.html</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
+  </script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="pointerlock_utils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="start();">
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=633602">
+    Mozilla Bug 633602
+  </a>
+  <div id="div"></div>
+  <script type="application/javascript">
+      /*
+       * Test for Bug 633602
+       * Confirm that screenX/Y and clientX/Y are constants when the pointer
+       * is locked.
+       */
+
+      SimpleTest.waitForExplicitFinish();
+
+      var div
+        , divRect
+        , unLockedCoords
+        , lockedCoords
+        , isUnlocked = false
+        , isLocked = false;
+
+      function runTests () {
+        ok(isUnlocked, "Pointer should be unlocked");
+        ok(isLocked, "Pointer should be locked");
+
+        // Confirm that pointer coords are constant while locked
+        is(unLockedCoords.clientX, lockedCoords.clientX,
+           "clientX should be equal to where the mouse was originaly locked");
+        is(unLockedCoords.clientY, lockedCoords.clientY,
+           "clientY should be equal to where the mouse was originaly locked");
+        is(unLockedCoords.screenX, lockedCoords.screenX,
+           "screenX should be equal to where the mouse was originaly locked");
+        is(unLockedCoords.screenY, lockedCoords.screenY,
+           "screenY should be equal to where the mouse was originaly locked");
+      }
+
+      function moveUnlocked(e) {
+        div.removeEventListener("mousemove", moveUnlocked, false);
+
+        isUnlocked = !document.mozPointerLockElement;
+        unLockedCoords = {
+          screenX: e.screenX,
+          screenY: e.screenY,
+          clientX: e.clientX,
+          clientY: e.clientY
+        };
+
+        div.mozRequestPointerLock();
+      }
+
+      function moveLocked(e) {
+        div.removeEventListener("mousemove", moveLocked, false);
+
+        isLocked = !!document.mozPointerLockElement;
+        lockedCoords = {
+          screenX: e.screenX,
+          screenY: e.screenY,
+          clientX: e.clientX,
+          clientY: e.clientY
+        };
+
+        document.mozCancelFullScreen();
+      }
+
+      document.addEventListener("mozpointerlockchange", function (e) {
+        if (document.mozPointerLockElement === div) {
+          div.addEventListener("mousemove", moveLocked, false);
+          divRect = div.getBoundingClientRect();
+          synthesizeMouse(div, (divRect.width / 4) * 3, (divRect.height / 4) * 3, {
+            type: "mousemove"
+          }, window);
+        }
+      }, false);
+
+      document.addEventListener("mozfullscreenchange", function() {
+        if (document.mozFullScreenElement === div) {
+          div.addEventListener("mousemove", moveUnlocked, false);
+          synthesizeMouseAtCenter(div, { type: "mousemove" }, window);
+        } else {
+          runTests();
+          SimpleTest.finish();
+        }
+      }, false);
+
+      function start() {
+        SimpleTest.waitForFocus(function() {
+          div = document.getElementById("div");
+          div.mozRequestFullScreen();
+        });
+      }
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/file_suppressSomeMouseEvents.html
@@ -0,0 +1,163 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=633602
+-->
+<head>
+  <title>Bug 633602 - file_cursorPosEvents.html</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
+  </script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="pointerlock_utils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <style type="text/css">
+    #child {
+      width: 100px;
+      height: 100px;
+      background-color:Green;
+    }
+  </style>
+</head>
+<body onload="start();">
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=633602">
+  Mozilla Bug 633602</a>
+
+  <div id="parent">
+    <div id="child"></div>
+  </div>
+
+  <script type="application/javascript">
+      /*
+       * Test for Bug 633602
+       * Test will check to make sure that the following mouse events are no
+       * longer executed in pointer lock.
+       *  - mouseover, mouseout, mouseenter, mouseleave
+       */
+
+      SimpleTest.waitForExplicitFinish();
+
+      function PointerEventStats() {
+        this.mouseEnter = false;
+        this.mouseLeave = false;
+        this.mouseOver = false;
+        this.mouseOut = false;
+      }
+
+      var parent
+        , child
+        , parentStats = new PointerEventStats()
+        , childStats = new PointerEventStats()
+        , isPointerLocked = false;
+
+      function runTests () {
+        ok(isPointerLocked, "expected mouse to be locked, but wasn't.");
+
+        is(childStats.mouseEnter, false,
+           "child's mouseenter should not be firing in Full Screen and Pointer Lock.");
+        is(childStats.mouseOver, false,
+           "child's mouseover should not be firing in Full Screen and Pointer Lock.");
+        is(childStats.mouseLeave, false,
+           "child's mouseleave should not be firing in Full Screen and Pointer Lock.");
+        is(childStats.mouseOut, false,
+           "child's mouseout should not be firing in Full Screen and Pointer Lock.");
+
+        is(parentStats.mouseEnter, false,
+           "parent's mouseenter should not be firing in Full Screen and Pointer Lock.");
+        is(parentStats.mouseOver, false,
+           "parent's mouseover should not be firing in Full Screen and Pointer Lock.");
+        is(parentStats.mouseLeave, false,
+           "parent's mouseleave should not be firing in Full Screen and Pointer Lock.");
+        is(parentStats.mouseOut, false,
+           "parent's mouseout should not be firing in Full Screen and Pointer Lock.");
+      }
+
+      var parentMoveListener = function () {
+        isPointerLocked = !!document.mozPointerLockElement;
+        removeEventListeners();
+        document.mozExitPointerLock();
+      };
+
+      var parentOutListener = function (e) {
+          parentStats.mouseOut = true;
+      };
+      var parentLeaveListener = function (e) {
+          parentStats.mouseLeave = true;
+      };
+      var parentOverListener = function (e) {
+          parentStats.mouseOver = true;
+      };
+      var parentEnterListener = function (e) {
+          parentStats.mouseEnter = true;
+      };
+
+      var childOutListener = function (e) {
+          childStats.mouseOut = true;
+      };
+      var childLeaveListener = function (e) {
+          childStats.mouseLeave = true;
+      };
+      var childOverListener = function (e) {
+          childStats.mouseOver = true;
+      };
+      var childEnterListener = function (e) {
+          childStats.mouseEnter = true;
+      };
+
+      function addEventListeners() {
+        parent.addEventListener("mousemove", parentMoveListener, false);
+
+        parent.addEventListener("mouseout", parentOutListener, false);
+        parent.addEventListener("mouseleave", parentLeaveListener, false);
+        parent.addEventListener("mouseover", parentOverListener, false);
+        parent.addEventListener("mouseenter", parentEnterListener, false);
+
+        child.addEventListener("mouseout", childOutListener, false);
+        child.addEventListener("mouseleave", childLeaveListener, false);
+        child.addEventListener("mouseover", childOverListener, false);
+        child.addEventListener("mouseenter", childEnterListener, false);
+      }
+
+      function removeEventListeners() {
+        parent.removeEventListener("mousemove", parentMoveListener, false);
+
+        parent.removeEventListener("mouseout", parentOutListener, false);
+        parent.removeEventListener("mouseleave", parentLeaveListener, false);
+        parent.removeEventListener("mouseover", parentOverListener, false);
+        parent.removeEventListener("mouseenter", parentEnterListener, false);
+
+        child.removeEventListener("mouseout", childOutListener, false);
+        child.removeEventListener("mouseleave", childLeaveListener, false);
+        child.removeEventListener("mouseover"  , childOverListener, false);
+        child.removeEventListener("mouseenter", childEnterListener, false);
+      }
+
+      document.addEventListener("mozpointerlockchange", function (e) {
+        if (document.mozPointerLockElement === parent) {
+          addEventListeners();
+          synthesizeMouseAtCenter(child, { type: "mousemove" }, window);
+        }
+        else {
+          document.mozCancelFullScreen();
+        }
+      }, false);
+
+      document.addEventListener("mozfullscreenchange", function() {
+        if (document.mozFullScreenElement === parent) {
+          parent.mozRequestPointerLock();
+        }
+        else {
+          runTests();
+          SimpleTest.finish();
+        }
+      }, false);
+
+      function start() {
+        SimpleTest.waitForFocus(function() {
+          parent = document.getElementById("parent");
+          child = document.getElementById("child");
+          parent.mozRequestFullScreen();
+        });
+      }
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/file_targetOutOfFocus.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+  <!--
+  https://bugzilla.mozilla.org/show_bug.cgi?id=633602
+  -->
+  <head>
+    <title>Bug 633602 - file_targetOutOfFocus.html</title>
+    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
+    </script>
+    <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js">
+    </script>
+    <script type="application/javascript" src="pointerlock_utils.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  </head>
+  <body onload="start();">
+    <a target="_blank"
+      href="https://bugzilla.mozilla.org/show_bug.cgi?id=633602">
+      Mozilla Bug 633602
+    </a>
+    <p id="display"></p>
+    <div id="content">
+    </div>
+    <div id="div"></div>
+    <input id="input" type="text" />
+    <pre id="test">
+      <script type="application/javascript">
+        /*
+         * Test for Bug 633602
+         * Element doesn't need to have focus to request
+         * pointer lock
+        */
+
+        SimpleTest.waitForExplicitFinish();
+
+        var div = document.getElementById("div")
+          , input = document.getElementById("input")
+          , divPointerLock = false;
+
+        function runTests () {
+          ok(divPointerLock, "Pointer should be locked even if " +
+            "the element being locked is not focused");
+        }
+
+        input.addEventListener("focus", function() {
+          div.mozRequestPointerLock();
+        }, false);
+
+        document.addEventListener("mozpointerlockchange", function (e) {
+          if (document.mozPointerLockElement === div) {
+            divPointerLock = true;
+            document.mozCancelFullScreen();
+          }
+        }, false);
+
+        document.addEventListener("mozfullscreenchange", function() {
+          if (document.mozFullScreenElement === div) {
+            input.focus();
+          }
+          else {
+            runTests();
+            SimpleTest.finish();
+          }
+        }, false);
+
+        function start() {
+          SimpleTest.waitForFocus(function() {
+            div.mozRequestFullScreen();
+          });
+        }
+      </script>
+    </pre>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/file_withoutDOM.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+  <!--
+  https://bugzilla.mozilla.org/show_bug.cgi?id=633602
+
+  Test DOM tree in full screen
+  -->
+  <head>
+    <title>Bug 633602 - file_DOMtree.html</title>
+    <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js">
+    </script>
+    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
+    </script>
+    <script type="application/javascript" src="pointerlock_utils.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+    <style>
+    </style>
+  </head>
+  <body onload="start();">
+    <a target="_blank"
+       href="https://bugzilla.mozilla.org/show_bug.cgi?id=633602">
+      Mozilla Bug 633602
+    </a>
+    <pre id="test">
+      <script type="text/javascript">
+        /*
+         * Test for Bug 633602
+         * Checks if element is attached to the DOM Tree before locking
+         * the pointer
+         */
+
+        SimpleTest.waitForExplicitFinish();
+
+        var div = document.createElement("div")
+          , withouthDOM = false;
+
+        function runTests () {
+          ok(withouthDOM, "If an element is NOT in the " +
+            "DOM Tree pointer should NOT be locked");
+        }
+
+        document.addEventListener("mozpointerlockerror", function (e) {
+          withouthDOM = true;
+          runTests();
+          SimpleTest.finish();
+        }, false);
+
+        function start() {
+          SimpleTest.waitForFocus(function() {
+            div.mozRequestPointerLock();
+          });
+        }
+      </script>
+    </pre>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/iframe_differentDOM.html
@@ -0,0 +1,7 @@
+<html>
+  <head>
+  </head>
+  <body>
+    <div id="div"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/pointerlock_utils.js
@@ -0,0 +1,34 @@
+// Get test filename for page being run in popup so errors are more useful
+var testName = location.pathname.split('/').pop();
+
+// Wrap test functions and pass to parent window
+function ok(a, msg) {
+  opener.ok(a, testName + ": " + msg);
+}
+
+function is(a, b, msg) {
+  opener.is(a, b, testName + ": " + msg);
+}
+
+function isnot(a, b, msg) {
+  opener.isnot(a, b, testName + ": " + msg);
+}
+
+function todo(a, msg) {
+  opener.todo(a, testName + ": " + msg);
+}
+
+function todo_is(a, b, msg) {
+  opener.todo_is(a, b, testName + ": " + msg);
+}
+
+function todo_isnot(a, b, msg) {
+  opener.todo_isnot(a, b, testName + ": " + msg);
+}
+
+// Override SimpleTest so test files work stand-alone
+SimpleTest.finish = function () {
+  opener.nextTest();
+};
+
+SimpleTest.waitForExplicitFinish = function() {};
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/pointerlock/test_pointerlock-api.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=633602
+-->
+  <head>
+    <title>Test for Bug 633602</title>
+    <script type="application/javascript"
+      src="/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="application/javascript"
+      src="/tests/SimpleTest/EventUtils.js"></script>
+    <link rel="stylesheet" type="text/css"
+          href="/tests/SimpleTest/test.css"/>
+  </head>
+  <body>
+    <a target="_blank"
+       href="https://bugzilla.mozilla.org/show_bug.cgi?id=633602">
+      Mozilla Bug 633602
+    </a>
+    <div id="content">
+    </div>
+    <pre id="test">
+      <script type="application/javascript">
+        SimpleTest.waitForExplicitFinish();
+        // Ensure the full-screen api is enabled,
+        // and will be disabled on test exit.
+        var prevEnabled = SpecialPowers.getBoolPref("full-screen-api.enabled");
+        SpecialPowers.setBoolPref("full-screen-api.enabled", true);
+
+        // Disable the requirement for trusted contexts only,
+        // so the tests are easier to write.
+        var prevTrusted = SpecialPowers.getBoolPref(
+          "full-screen-api.allow-trusted-requests-only");
+        SpecialPowers.setBoolPref("full-screen-api.allow-trusted-requests-only",
+                                  false);
+
+        // Run the tests which go full-screen in new window, as Mochitests
+        // normally run in an iframe, which by default will not have the
+        // mozallowfullscreen attribute set, so full-screen won't work.
+        var gTestFiles = [
+          "file_childIframe.html",
+          "file_doubleLock.html",
+          "file_escapeKey.html",
+          "file_infiniteMovement.html",
+          "file_locksvgelement.html",
+          "file_loseFocusWindow.html",
+          "file_movementXY.html",
+          "file_nestedFullScreen.html",
+          "file_pointerlock-api.html",
+          "file_pointerlockerror.html",
+          "file_pointerLockPref.html",
+          "file_removedFromDOM.html",
+          "file_retargetMouseEvents.html",
+          "file_screenClientXYConst.html",
+          "file_suppressSomeMouseEvents.html",
+          "file_targetOutOfFocus.html",
+          "file_withoutDOM.html"
+        ];
+
+        var testWindow = null;
+        var gTestIndex = 0;
+
+        function nextTest() {
+          if (testWindow) {
+            testWindow.close();
+          }
+          if (gTestIndex < gTestFiles.length) {
+            testWindow = window.open(gTestFiles[gTestIndex],
+                                     "",
+                                     "width=500,height=500");
+            gTestIndex++;
+          } else {
+            SpecialPowers.setBoolPref("full-screen-api.enabled",
+                                      prevEnabled);
+            SpecialPowers.setBoolPref("full-screen-api.allow-trusted-requests-only",
+                                      prevTrusted);
+            SimpleTest.finish();
+          }
+        }
+
+        addLoadEvent(nextTest);
+      </script>
+    </pre>
+  </body>
+</html>
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -129,20 +129,23 @@ class LayerManager;
 // Flags to pass to SetCapturingContent
 //
 // when assigning capture, ignore whether capture is allowed or not
 #define CAPTURE_IGNOREALLOWED 1
 // true if events should be targeted at the capturing content or its children
 #define CAPTURE_RETARGETTOELEMENT 2
 // true if the current capture wants drags to be prevented
 #define CAPTURE_PREVENTDRAG 4
+// true when the mouse is pointer locked, and events are sent to locked elemnt
+#define CAPTURE_POINTERLOCK 8
 
 typedef struct CapturingContentInfo {
   // capture should only be allowed during a mousedown event
   bool mAllowed;
+  bool mPointerLock;
   bool mRetargetToElement;
   bool mPreventDrag;
   nsIContent* mContent;
 } CapturingContentInfo;
 
 #define NS_IPRESSHELL_IID    \
         { 0x4dc4db09, 0x03d4, 0x4427, \
           { 0xbe, 0xfb, 0xc9, 0x29, 0xac, 0x5c, 0x62, 0xab } }
@@ -1098,16 +1101,21 @@ public:
    * If CAPTURE_RETARGETTOELEMENT is set, all mouse events are targeted at
    * aContent only. Otherwise, mouse events are targeted at aContent or its
    * descendants. That is, descendants of aContent receive mouse events as
    * they normally would, but mouse events outside of aContent are retargeted
    * to aContent.
    *
    * If CAPTURE_PREVENTDRAG is set then drags are prevented from starting while
    * this capture is active.
+   *
+   * If CAPTURE_POINTERLOCK is set, similar to CAPTURE_RETARGETTOELEMENT, then
+   * events are targeted at aContent, but capturing is held more strongly (i.e.,
+   * calls to SetCapturingContent won't unlock unless CAPTURE_POINTERLOCK is
+   * set again).
    */
   static void SetCapturingContent(nsIContent* aContent, PRUint8 aFlags);
 
   /**
    * Return the active content currently capturing the mouse if any.
    */
   static nsIContent* GetCapturingContent()
   {
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -223,17 +223,17 @@
 
 #define ANCHOR_SCROLL_FLAGS (SCROLL_OVERFLOW_HIDDEN | SCROLL_NO_PARENT_FRAMES)
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::layers;
 
 CapturingContentInfo nsIPresShell::gCaptureInfo =
-  { false /* mAllowed */,     false /* mRetargetToElement */,
+  { false /* mAllowed */, false /* mPointerLock */, false /* mRetargetToElement */,
     false /* mPreventDrag */, nsnull /* mContent */ };
 nsIContent* nsIPresShell::gKeyDownTarget;
 nsInterfaceHashtable<nsUint32HashKey, nsIDOMTouch> nsIPresShell::gCaptureTouchList;
 bool nsIPresShell::gPreventMouseEvents = false;
 
 static PRUint32
 ChangeFlag(PRUint32 aFlags, bool aOnOff, PRUint32 aFlag)
 {
@@ -5442,26 +5442,37 @@ PresShell::Paint(nsIView*           aVie
 
   presContext->NotifyDidPaintForSubtree();
 }
 
 // static
 void
 nsIPresShell::SetCapturingContent(nsIContent* aContent, PRUint8 aFlags)
 {
+  // If capture was set for pointer lock, don't unlock unless we are coming
+  // out of pointer lock explicitly.
+  if (!aContent && gCaptureInfo.mPointerLock &&
+      !(aFlags & CAPTURE_POINTERLOCK)) {
+    return;
+  }
+
   NS_IF_RELEASE(gCaptureInfo.mContent);
 
-  // only set capturing content if allowed or the CAPTURE_IGNOREALLOWED flag
-  // is used
-  if ((aFlags & CAPTURE_IGNOREALLOWED) || gCaptureInfo.mAllowed) {
+  // only set capturing content if allowed or the CAPTURE_IGNOREALLOWED or
+  // CAPTURE_POINTERLOCK flags are used.
+  if ((aFlags & CAPTURE_IGNOREALLOWED) || gCaptureInfo.mAllowed ||
+      (aFlags & CAPTURE_POINTERLOCK)) {
     if (aContent) {
       NS_ADDREF(gCaptureInfo.mContent = aContent);
     }
-    gCaptureInfo.mRetargetToElement = (aFlags & CAPTURE_RETARGETTOELEMENT) != 0;
+    // CAPTURE_POINTERLOCK is the same as CAPTURE_RETARGETTOELEMENT & CAPTURE_IGNOREALLOWED
+    gCaptureInfo.mRetargetToElement = ((aFlags & CAPTURE_RETARGETTOELEMENT) != 0) ||
+                                      ((aFlags & CAPTURE_POINTERLOCK) != 0);
     gCaptureInfo.mPreventDrag = (aFlags & CAPTURE_PREVENTDRAG) != 0;
+    gCaptureInfo.mPointerLock = (aFlags & CAPTURE_POINTERLOCK) != 0;
   }
 }
 
 nsIFrame*
 PresShell::GetCurrentEventFrame()
 {
   if (NS_UNLIKELY(mIsDestroying)) {
     return nsnull;
@@ -5745,17 +5756,18 @@ PresShell::HandleEvent(nsIFrame        *
 #endif
 
   if (!nsContentUtils::IsSafeToRunScript())
     return NS_OK;
 
   NS_TIME_FUNCTION_MIN(1.0);
 
   nsIContent* capturingContent =
-    NS_IS_MOUSE_EVENT(aEvent) ? GetCapturingContent() : nsnull;
+    NS_IS_MOUSE_EVENT(aEvent) || aEvent->eventStructType == NS_MOUSE_SCROLL_EVENT ?
+      GetCapturingContent() : nsnull;
 
   nsCOMPtr<nsIDocument> retargetEventDoc;
   if (!aDontRetargetEvents) {
     // key and IME related events should not cross top level window boundary.
     // Basically, such input events should be fired only on focused widget.
     // However, some IMEs might need to clean up composition after focused
     // window is deactivated.  And also some tests on MozMill want to test key
     // handling on deactivated window because MozMill window can be activated
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -3481,16 +3481,17 @@ pref("alerts.totalOpenTime", 4000);
 pref("alerts.disableSlidingEffect", false);
 
 // DOM full-screen API.
 pref("full-screen-api.enabled", false);
 pref("full-screen-api.allow-trusted-requests-only", true);
 pref("full-screen-api.key-input-restricted", true);
 pref("full-screen-api.warning.enabled", true);
 pref("full-screen-api.exit-on-deactivate", true);
+pref("full-screen-api.pointer-lock.enabled", true);
 
 // Time limit, in milliseconds, for nsEventStateManager::IsHandlingUserInput().
 // Used to detect long running handlers of user-generated events.
 pref("dom.event.handling-user-input-time-limit", 1000);
  
 //3D Transforms
 pref("layout.3d-transforms.enabled", true);
 
--- a/widget/cocoa/nsChildView.h
+++ b/widget/cocoa/nsChildView.h
@@ -483,17 +483,20 @@ public:
                                             PRInt32 aNativeKeyCode,
                                             PRUint32 aModifierFlags,
                                             const nsAString& aCharacters,
                                             const nsAString& aUnmodifiedCharacters);
 
   virtual nsresult SynthesizeNativeMouseEvent(nsIntPoint aPoint,
                                               PRUint32 aNativeMessage,
                                               PRUint32 aModifierFlags);
-  
+
+  virtual nsresult SynthesizeNativeMouseMove(nsIntPoint aPoint)
+  { return SynthesizeNativeMouseEvent(aPoint, NSMouseMoved, 0); }
+
   // Mac specific methods
   
   virtual bool      DispatchWindowEvent(nsGUIEvent& event);
   
 #ifdef ACCESSIBILITY
   already_AddRefed<nsAccessible> GetDocumentAccessible();
 #endif
 
--- a/widget/gtk2/nsWindow.cpp
+++ b/widget/gtk2/nsWindow.cpp
@@ -6488,8 +6488,24 @@ nsWindow::ClearCachedResources()
     GList* children = gdk_window_peek_children(mGdkWindow);
     for (GList* list = children; list; list = list->next) {
         nsWindow* window = get_window_for_gdk_window(GDK_WINDOW(list->data));
         if (window) {
             window->ClearCachedResources();
         }
     }
 }
+
+nsresult
+nsWindow::SynthesizeNativeMouseEvent(nsIntPoint aPoint,
+                                     PRUint32 aNativeMessage,
+                                     PRUint32 aModifierFlags)
+{
+  if (!mGdkWindow) {
+    return NS_OK;
+  }
+
+  GdkDisplay* display = gdk_window_get_display(mGdkWindow);
+  GdkScreen* screen = gdk_window_get_screen(mGdkWindow);
+  gdk_display_warp_pointer(display, screen, aPoint.x, aPoint.y);
+
+  return NS_OK;
+}
--- a/widget/gtk2/nsWindow.h
+++ b/widget/gtk2/nsWindow.h
@@ -342,16 +342,23 @@ public:
 
     static already_AddRefed<gfxASurface> GetSurfaceForGdkDrawable(GdkDrawable* aDrawable,
                                                                   const nsIntSize& aSize);
 #else
     gfxASurface       *GetThebesSurface(cairo_t *cr);
 #endif
     NS_IMETHOD         ReparentNativeWidget(nsIWidget* aNewParent);
 
+    virtual nsresult SynthesizeNativeMouseEvent(nsIntPoint aPoint,
+                                                PRUint32 aNativeMessage,
+                                                PRUint32 aModifierFlags);
+
+    virtual nsresult SynthesizeNativeMouseMove(nsIntPoint aPoint)
+    { return SynthesizeNativeMouseEvent(aPoint, GDK_MOTION_NOTIFY, 0); }
+
 protected:
     // Helper for SetParent and ReparentNativeWidget.
     void ReparentNativeWidgetInternal(nsIWidget* aNewParent,
                                       GtkWidget* aNewContainer,
                                       GdkWindow* aNewParentWindow,
                                       GtkWidget* aOldContainer);
     nsCOMPtr<nsIWidget> mParent;
     // Is this a toplevel window?
--- a/widget/nsGUIEvent.h
+++ b/widget/nsGUIEvent.h
@@ -553,16 +553,21 @@ class nsHashKey;
 #define NS_TOUCH_EVENT_START         5200
 #define NS_TOUCH_START               (NS_TOUCH_EVENT_START)
 #define NS_TOUCH_MOVE                (NS_TOUCH_EVENT_START+1)
 #define NS_TOUCH_END                 (NS_TOUCH_EVENT_START+2)
 #define NS_TOUCH_ENTER               (NS_TOUCH_EVENT_START+3)
 #define NS_TOUCH_LEAVE               (NS_TOUCH_EVENT_START+4)
 #define NS_TOUCH_CANCEL              (NS_TOUCH_EVENT_START+5)
 
+// Pointerlock DOM API
+#define NS_POINTERLOCK_START         5300
+#define NS_POINTERLOCKCHANGE         (NS_POINTERLOCK_START)
+#define NS_POINTERLOCKERROR          (NS_POINTERLOCK_START + 1)
+
 /**
  * Return status for event processors, nsEventStatus, is defined in
  * nsEvent.h.
  */
 
 /**
  * different types of (top-level) window z-level positioning
  */
@@ -578,32 +583,34 @@ enum nsWindowZ {
 
 class nsEvent
 {
 protected:
   nsEvent(bool isTrusted, PRUint32 msg, PRUint8 structType)
     : eventStructType(structType),
       message(msg),
       refPoint(0, 0),
+      lastRefPoint(0, 0),
       time(0),
       flags(isTrusted ? NS_EVENT_FLAG_TRUSTED : NS_EVENT_FLAG_NONE),
       userType(0)
   {
     MOZ_COUNT_CTOR(nsEvent);
   }
 
   nsEvent()
   {
   }
 
 public:
   nsEvent(bool isTrusted, PRUint32 msg)
     : eventStructType(NS_EVENT),
       message(msg),
       refPoint(0, 0),
+      lastRefPoint(0, 0),
       time(0),
       flags(isTrusted ? NS_EVENT_FLAG_TRUSTED : NS_EVENT_FLAG_NONE),
       userType(0)
   {
     MOZ_COUNT_CTOR(nsEvent);
   }
 
   ~nsEvent()
@@ -613,16 +620,18 @@ public:
 
   // See event struct types
   PRUint8     eventStructType;
   // See GUI MESSAGES,
   PRUint32    message;
   // Relative to the widget of the event, or if there is no widget then it is
   // in screen coordinates. Not modified by layout code.
   nsIntPoint  refPoint;
+  // The previous refPoint, if known, used to calculate mouse movement deltas.
+  nsIntPoint  lastRefPoint;
   // Elapsed time, in milliseconds, from a platform-specific zero time
   // to the time the message was created
   PRUint64    time;
   // Flags to hold event flow stage and capture/bubble cancellation
   // status. This is used also to indicate whether the event is trusted.
   PRUint32    flags;
   // Additional type info for user defined events
   nsCOMPtr<nsIAtom>     userType;
--- a/widget/nsIWidget.h
+++ b/widget/nsIWidget.h
@@ -113,18 +113,18 @@ typedef nsEventStatus (* EVENT_CALLBACK)
 #endif
 #ifdef XP_WIN
 #define NS_NATIVE_TSF_THREAD_MGR       100
 #define NS_NATIVE_TSF_CATEGORY_MGR     101
 #define NS_NATIVE_TSF_DISPLAY_ATTR_MGR 102
 #endif
 
 #define NS_IWIDGET_IID \
-  { 0xe7af49c1, 0xd11b, 0x4070, \
-    { 0x99, 0x7a, 0x2d, 0x2b, 0x7, 0x4b, 0xea, 0xf4 } }
+  { 0xb5bb55c7, 0x9a50, 0x4fa8, \
+    { 0xa7, 0x6e, 0xbd, 0x31, 0x6f, 0x3e, 0x9c, 0x13 } }
 
 /*
  * Window shadow styles
  * Also used for the -moz-window-shadow CSS property
  */
 
 #define NS_STYLE_WINDOW_SHADOW_NONE             0
 #define NS_STYLE_WINDOW_SHADOW_DEFAULT          1
@@ -1372,16 +1372,21 @@ class nsIWidget : public nsISupports {
      * @param aModifierFlags *platform-specific* modifier flags (ignored
      * on Windows)
      */
     virtual nsresult SynthesizeNativeMouseEvent(nsIntPoint aPoint,
                                                 PRUint32 aNativeMessage,
                                                 PRUint32 aModifierFlags) = 0;
 
     /**
+     * A shortcut to SynthesizeNativeMouseEvent, abstracting away the native message.
+     */
+    virtual nsresult SynthesizeNativeMouseMove(nsIntPoint aPoint) = 0;
+
+    /**
      * Utility method intended for testing. Dispatching native mouse scroll
      * events may move the mouse cursor.
      *
      * @param aPoint            Mouse cursor position in screen coordinates.
      *                          In device pixels, the origin at the top left of
      *                          the primary display.
      * @param aNativeMessage    Platform native message.
      * @param aDeltaX           The delta value for X direction.  If the native
--- a/widget/windows/nsWindow.h
+++ b/widget/windows/nsWindow.h
@@ -172,16 +172,20 @@ public:
   virtual nsresult        SynthesizeNativeKeyEvent(PRInt32 aNativeKeyboardLayout,
                                                    PRInt32 aNativeKeyCode,
                                                    PRUint32 aModifierFlags,
                                                    const nsAString& aCharacters,
                                                    const nsAString& aUnmodifiedCharacters);
   virtual nsresult        SynthesizeNativeMouseEvent(nsIntPoint aPoint,
                                                      PRUint32 aNativeMessage,
                                                      PRUint32 aModifierFlags);
+
+  virtual nsresult        SynthesizeNativeMouseMove(nsIntPoint aPoint)
+                          { return SynthesizeNativeMouseEvent(aPoint, MOUSEEVENTF_MOVE, 0); }
+
   virtual nsresult        SynthesizeNativeMouseScrollEvent(nsIntPoint aPoint,
                                                            PRUint32 aNativeMessage,
                                                            double aDeltaX,
                                                            double aDeltaY,
                                                            double aDeltaZ,
                                                            PRUint32 aModifierFlags,
                                                            PRUint32 aAdditionalFlags);
   NS_IMETHOD              ResetInputState();
--- a/widget/xpwidgets/nsBaseWidget.h
+++ b/widget/xpwidgets/nsBaseWidget.h
@@ -265,16 +265,19 @@ protected:
                                             const nsAString& aUnmodifiedCharacters)
   { return NS_ERROR_UNEXPECTED; }
 
   virtual nsresult SynthesizeNativeMouseEvent(nsIntPoint aPoint,
                                               PRUint32 aNativeMessage,
                                               PRUint32 aModifierFlags)
   { return NS_ERROR_UNEXPECTED; }
 
+  virtual nsresult SynthesizeNativeMouseMove(nsIntPoint aPoint)
+  { return NS_ERROR_UNEXPECTED; }
+
   virtual nsresult SynthesizeNativeMouseScrollEvent(nsIntPoint aPoint,
                                                     PRUint32 aNativeMessage,
                                                     double aDeltaX,
                                                     double aDeltaY,
                                                     double aDeltaZ,
                                                     PRUint32 aModifierFlags,
                                                     PRUint32 aAdditionalFlags)
   { return NS_ERROR_UNEXPECTED; }