Pointer Lock handles disconnected DOM elements
authorscheib@chromium.org <scheib@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 13 Jul 2012 20:37:50 +0000 (20:37 +0000)
committerscheib@chromium.org <scheib@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 13 Jul 2012 20:37:50 +0000 (20:37 +0000)
https://bugs.webkit.org/show_bug.cgi?id=77029

Reviewed by Adrienne Walker.

Source/WebCore:

Pointer Lock Controller now checks when elements or documents are
removed, and unlocks if the target element is being removed.

Tests: pointer-lock/locked-element-iframe-removed-from-dom.html
       pointer-lock/locked-element-removed-from-dom.html

* dom/Document.cpp:
(WebCore::Document::detach):
* dom/Element.cpp:
(WebCore::Element::removedFrom):
(WebCore::Element::webkitRequestPointerLock):
* page/PointerLockController.cpp:
(WebCore::PointerLockController::requestPointerLock):
(WebCore::PointerLockController::elementRemoved):
(WebCore):
(WebCore::PointerLockController::documentDetached):
(WebCore::PointerLockController::didLosePointerLock):
(WebCore::PointerLockController::enqueueEvent):
* page/PointerLockController.h:
(WebCore):
(PointerLockController):

LayoutTests:

Two new tests that verify pointer lock is released when the target
is removed from the document.

* pointer-lock/locked-element-iframe-removed-from-dom-expected.txt: Added.
* pointer-lock/locked-element-iframe-removed-from-dom.html: Added.
* pointer-lock/locked-element-removed-from-dom-expected.txt: Added.
* pointer-lock/locked-element-removed-from-dom.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@122626 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/pointer-lock/locked-element-iframe-removed-from-dom-expected.txt [new file with mode: 0644]
LayoutTests/pointer-lock/locked-element-iframe-removed-from-dom.html [new file with mode: 0644]
LayoutTests/pointer-lock/locked-element-removed-from-dom-expected.txt [new file with mode: 0644]
LayoutTests/pointer-lock/locked-element-removed-from-dom.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/Element.cpp
Source/WebCore/page/PointerLockController.cpp
Source/WebCore/page/PointerLockController.h

index 8a13bee..3e67d2b 100644 (file)
@@ -1,3 +1,18 @@
+2012-07-13  Vincent Scheib  <scheib@chromium.org>
+
+        Pointer Lock handles disconnected DOM elements
+        https://bugs.webkit.org/show_bug.cgi?id=77029
+
+        Reviewed by Adrienne Walker.
+
+        Two new tests that verify pointer lock is released when the target
+        is removed from the document.
+
+        * pointer-lock/locked-element-iframe-removed-from-dom-expected.txt: Added.
+        * pointer-lock/locked-element-iframe-removed-from-dom.html: Added.
+        * pointer-lock/locked-element-removed-from-dom-expected.txt: Added.
+        * pointer-lock/locked-element-removed-from-dom.html: Added.
+
 2012-07-13  W. James MacLean  <wjmaclean@chromium.org>
 
         [chromium] Unreviewed gardening. storage/indexeddb/cursor-key-order.html has starting crashing on occassion.
diff --git a/LayoutTests/pointer-lock/locked-element-iframe-removed-from-dom-expected.txt b/LayoutTests/pointer-lock/locked-element-iframe-removed-from-dom-expected.txt
new file mode 100644 (file)
index 0000000..3092dec
--- /dev/null
@@ -0,0 +1,18 @@
+Test removing an iframe containing a locked element causes lock to be released.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+     Lock target in iframe. (main document handler)
+     Lock target in iframe. (iframe handler)
+PASS onwebkitpointerlockchange received after: Lock target in iframe. (iframe handler)
+PASS document.webkitPointerLockElement is targetDiv1
+PASS targetDiv1.parentElement.parentElement is targetIframe1.contentDocument.body
+     Remove iframe & immediately lock target2. (main document handler)
+     Remove iframe & immediately lock target2. (iframe handler)
+PASS document.webkitPointerLockElement is targetDiv2
+PASS onwebkitpointerlockchange received after: Remove iframe & immediately lock target2. (main document handler)
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/pointer-lock/locked-element-iframe-removed-from-dom.html b/LayoutTests/pointer-lock/locked-element-iframe-removed-from-dom.html
new file mode 100644 (file)
index 0000000..32f68d8
--- /dev/null
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+<script src="resources/pointer-lock-test-harness.js"></script>
+</head>
+<body>
+<div>
+  <iframe id="iframe1"></iframe>
+  <div id="target2"></div>
+</div>
+<script>
+    description("Test removing an iframe containing a locked element causes lock to be released.")
+    window.jsTestIsAsync = true;
+
+    targetIframe1 = document.getElementById("iframe1");
+    targetDiv2 = document.getElementById("target2");
+
+    todo = [
+        function () {
+            // Load a blank iframe.
+            targetIframe1.src = "about:blank";
+            targetIframe1.onload = function () { doNextStepWithUserGesture(); }
+        },
+        function () {
+            // Nest target element into iframe document.
+            targetIframe1.contentDocument.body.innerHTML ="<div><div></div></div>";
+            targetDiv1 = targetIframe1.contentDocument.body.firstChild.firstChild
+            expectNoEvents("Lock target in iframe. (main document handler)");
+            expectOnlyChangeEvent("Lock target in iframe. (iframe handler)", targetIframe1.contentDocument);
+            targetDiv1.webkitRequestPointerLock();
+            // doNextStep called by event handler.
+        },
+        function () {
+            shouldBe("document.webkitPointerLockElement", "targetDiv1");
+            shouldBe("targetDiv1.parentElement.parentElement", "targetIframe1.contentDocument.body");
+            expectOnlyChangeEvent("Remove iframe & immediately lock target2. (main document handler)");
+            expectNoEvents("Remove iframe & immediately lock target2. (iframe handler)", targetIframe1.contentDocument);
+            targetIframe1.parentElement.removeChild(targetIframe1);
+            targetDiv2.webkitRequestPointerLock();
+            shouldBe("document.webkitPointerLockElement", "targetDiv2");
+            // doNextStep called by event handler.
+        },
+    ];
+    doNextStep();
+</script>
+<script src="../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/pointer-lock/locked-element-removed-from-dom-expected.txt b/LayoutTests/pointer-lock/locked-element-removed-from-dom-expected.txt
new file mode 100644 (file)
index 0000000..aaf8a5b
--- /dev/null
@@ -0,0 +1,21 @@
+Test removing a locked element from a document causes lock to be released.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+     Lock target in iframe. (main document handler).
+     Lock target in iframe. (iframe handler)
+PASS onwebkitpointerlockchange received after: Lock target in iframe. (iframe handler)
+PASS document.webkitPointerLockElement is targetDiv1
+PASS targetDiv1.parentElement.parentElement is targetIframe1.contentDocument.body
+     Remove targetDiv1's parent from iframe & immediately lock target2. (main document handler)
+     Remove targetDiv1's parent from iframe & immediately lock target2. (iframe handler)
+PASS document.webkitPointerLockElement is null
+PASS targetDiv1.parentElement.parentElement is null
+PASS onwebkitpointerlockerror received after: Remove targetDiv1's parent from iframe & immediately lock target2. (main document handler)
+PASS onwebkitpointerlockchange received after: Remove targetDiv1's parent from iframe & immediately lock target2. (iframe handler)
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
+
diff --git a/LayoutTests/pointer-lock/locked-element-removed-from-dom.html b/LayoutTests/pointer-lock/locked-element-removed-from-dom.html
new file mode 100644 (file)
index 0000000..d71b8ce
--- /dev/null
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+<script src="resources/pointer-lock-test-harness.js"></script>
+</head>
+<body>
+<div>
+  <iframe id="iframe1"></iframe>
+  <div id="target2"></div>
+</div>
+<script>
+    description("Test removing a locked element from a document causes lock to be released.")
+    window.jsTestIsAsync = true;
+
+    targetIframe1 = document.getElementById("iframe1");
+    targetDiv2 = document.getElementById("target2");
+
+    todo = [
+        function () {
+            // Load a blank iframe.
+            targetIframe1.src = "about:blank";
+            targetIframe1.onload = function () { doNextStepWithUserGesture(); }
+        },
+        function () {
+            // Nest target element into iframe document.
+            targetIframe1.contentDocument.body.innerHTML ="<div><div></div></div>";
+            targetDiv1 = targetIframe1.contentDocument.body.firstChild.firstChild
+            expectNoEvents("Lock target in iframe. (main document handler).");
+            expectOnlyChangeEvent("Lock target in iframe. (iframe handler)", targetIframe1.contentDocument);
+            targetDiv1.webkitRequestPointerLock();
+            // doNextStep called by event handler.
+        },
+        function () {
+            shouldBe("document.webkitPointerLockElement", "targetDiv1");
+            shouldBe("targetDiv1.parentElement.parentElement", "targetIframe1.contentDocument.body");
+            expectOnlyErrorEvent("Remove targetDiv1's parent from iframe & immediately lock target2. (main document handler)");
+            expectOnlyChangeEvent("Remove targetDiv1's parent from iframe & immediately lock target2. (iframe handler)", targetIframe1.contentDocument);
+            targetDiv1.parentElement.parentElement.removeChild(targetDiv1.parentElement);
+            targetDiv2.webkitRequestPointerLock();
+            shouldBe("document.webkitPointerLockElement", "null");
+            shouldBe("targetDiv1.parentElement.parentElement", "null");
+            // doNextStep called by event handler.
+        },
+    ];
+    doNextStep();
+</script>
+<script src="../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
index 89b8e37..c244b19 100644 (file)
@@ -1,3 +1,32 @@
+2012-07-13  Vincent Scheib  <scheib@chromium.org>
+
+        Pointer Lock handles disconnected DOM elements
+        https://bugs.webkit.org/show_bug.cgi?id=77029
+
+        Reviewed by Adrienne Walker.
+
+        Pointer Lock Controller now checks when elements or documents are
+        removed, and unlocks if the target element is being removed.
+
+        Tests: pointer-lock/locked-element-iframe-removed-from-dom.html
+               pointer-lock/locked-element-removed-from-dom.html
+
+        * dom/Document.cpp:
+        (WebCore::Document::detach):
+        * dom/Element.cpp:
+        (WebCore::Element::removedFrom):
+        (WebCore::Element::webkitRequestPointerLock):
+        * page/PointerLockController.cpp:
+        (WebCore::PointerLockController::requestPointerLock):
+        (WebCore::PointerLockController::elementRemoved):
+        (WebCore):
+        (WebCore::PointerLockController::documentDetached):
+        (WebCore::PointerLockController::didLosePointerLock):
+        (WebCore::PointerLockController::enqueueEvent):
+        * page/PointerLockController.h:
+        (WebCore):
+        (PointerLockController):
+
 2012-07-13  Ryosuke Niwa  <rniwa@webkit.org>
 
         HTMLCollection should use DynamicNodeList's invalidation model
index 73dccb7..b4fa8a2 100644 (file)
@@ -2080,6 +2080,11 @@ void Document::detach()
     ASSERT(attached());
     ASSERT(!m_inPageCache);
 
+#if ENABLE(POINTER_LOCK)
+    if (page())
+        page()->pointerLockController()->documentDetached(this);
+#endif
+
     if (this == topDocument())
         clearAXObjectCache();
 
index f65d361..869c344 100644 (file)
@@ -913,6 +913,10 @@ void Element::removedFrom(ContainerNode* insertionPoint)
     if (containsFullScreenElement())
         setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false);
 #endif
+#if ENABLE(POINTER_LOCK)
+    if (document()->page())
+        document()->page()->pointerLockController()->elementRemoved(this);
+#endif
 
     setSavedLayerScrollOffset(IntSize());
 
@@ -1890,7 +1894,8 @@ void Element::setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(boo
 #if ENABLE(POINTER_LOCK)
 void Element::webkitRequestPointerLock()
 {
-    document()->frame()->page()->pointerLockController()->requestPointerLock(this, 0, 0);
+    if (document()->page())
+        document()->page()->pointerLockController()->requestPointerLock(this, 0, 0);
 }
 #endif
 
index c250cb7..abbfc80 100644 (file)
@@ -48,15 +48,12 @@ PassOwnPtr<PointerLockController> PointerLockController::create(Page* page)
 
 void PointerLockController::requestPointerLock(Element* target, PassRefPtr<VoidCallback> successCallback, PassRefPtr<VoidCallback> failureCallback)
 {
-    if (!target)
-        return;
-
-    if (!target->inDocument()) {
+    if (!target || !target->inDocument() || m_documentOfRemovedElementWhileWaitingForUnlock) {
         enqueueEvent(eventNames().webkitpointerlockerrorEvent, target);
         return;
     }
 
-    if (isLocked()) {
+    if (m_element) {
         // FIXME: Keep enqueueEvent usage. (https://bugs.webkit.org/show_bug.cgi?id=84402)
         enqueueEvent(eventNames().webkitpointerlockchangeEvent, target);
         if (m_element->document() != target->document())
@@ -91,6 +88,25 @@ void PointerLockController::requestPointerUnlock()
     return m_page->chrome()->client()->requestPointerUnlock();
 }
 
+void PointerLockController::elementRemoved(Element* element)
+{
+    if (m_element == element) {
+        m_documentOfRemovedElementWhileWaitingForUnlock = m_element->document();
+        // Set element null immediately to block any future interaction with it
+        // including mouse events received before the unlock completes.
+        m_element = 0;
+        requestPointerUnlock();
+    }
+}
+
+void PointerLockController::documentDetached(Document* document)
+{
+    if (m_element && m_element->document() == document) {
+        m_element = 0;
+        requestPointerUnlock();
+    }
+}
+
 bool PointerLockController::isLocked()
 {
     return m_page->chrome()->client()->isPointerLocked();
@@ -136,11 +152,12 @@ void PointerLockController::didLosePointerLock(bool sendChangeEvent)
 {
     // FIXME: Keep enqueueEvent usage. (https://bugs.webkit.org/show_bug.cgi?id=84402)
     if (sendChangeEvent)
-        enqueueEvent(eventNames().webkitpointerlockchangeEvent, m_element.get());
+        enqueueEvent(eventNames().webkitpointerlockchangeEvent, m_element ? m_element->document() : m_documentOfRemovedElementWhileWaitingForUnlock.get());
 
     // FIXME: Remove callback usage. (https://bugs.webkit.org/show_bug.cgi?id=84402)
     RefPtr<Element> elementToNotify(m_element);
     m_element = 0;
+    m_documentOfRemovedElementWhileWaitingForUnlock = 0;
     m_successCallback = 0;
     m_failureCallback = 0;
     if (elementToNotify && elementToNotify->document()->frame())
@@ -161,9 +178,14 @@ void PointerLockController::dispatchLockedMouseEvent(const PlatformMouseEvent& e
 
 void PointerLockController::enqueueEvent(const AtomicString& type, Element* element)
 {
-    if (!element)
-        return;
-    element->document()->enqueueDocumentEvent(Event::create(type, true, false));
+    if (element)
+        enqueueEvent(type, element->document());
+}
+
+void PointerLockController::enqueueEvent(const AtomicString& type, Document* document)
+{
+    if (document)
+        document->enqueueDocumentEvent(Event::create(type, true, false));
 }
 
 } // namespace WebCore
index 55cf6ee..991754e 100644 (file)
@@ -33,6 +33,7 @@
 namespace WebCore {
 
 class Element;
+class Document;
 class Page;
 class PlatformMouseEvent;
 class VoidCallback;
@@ -45,7 +46,9 @@ public:
 
     void requestPointerLock(Element* target, PassRefPtr<VoidCallback> successCallback, PassRefPtr<VoidCallback> failureCallback);
     void requestPointerUnlock();
-    bool isLocked();
+    void elementRemoved(Element*);
+    void documentDetached(Document*);
+    bool isLocked(); // FIXME: Rename to isClientLocked and move to private when removing old API. (https://bugs.webkit.org/show_bug.cgi?id=84402)
     Element* element() const;
 
     void didAcquirePointerLock();
@@ -56,8 +59,10 @@ public:
 private:
     explicit PointerLockController(Page*);
     void enqueueEvent(const AtomicString& type, Element*);
+    void enqueueEvent(const AtomicString& type, Document*);
     Page* m_page;
     RefPtr<Element> m_element;
+    RefPtr<Document> m_documentOfRemovedElementWhileWaitingForUnlock;
 
     // FIXME: Remove callback usage. (https://bugs.webkit.org/show_bug.cgi?id=84402)
     RefPtr<VoidCallback> m_successCallback;