author | David Humphrey <david.humphrey@senecac.on.ca> |
Wed, 28 Mar 2012 21:00:14 -0400 | |
changeset 90561 | dcf8929a8115a4a90e673f9dbf5c857857307445 |
parent 90560 | 082d5e045cc2009c1b7709b85b2684925759e7d1 |
child 90562 | 095fd525afa782e7f4c23a7056b1810f512284ee |
push id | 7726 |
push user | ryanvm@gmail.com |
push date | Thu, 29 Mar 2012 01:00:28 +0000 |
treeherder | mozilla-inbound@0cb85ff0f764 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | roc, smaug |
bugs | 633602 |
milestone | 14.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
|
--- 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; }