Title: [209628] trunk
Revision
209628
Author
rn...@webkit.org
Date
2016-12-09 14:06:29 -0800 (Fri, 09 Dec 2016)

Log Message

document.webkitFullscreenElement leaks elements inside a shadow tree
https://bugs.webkit.org/show_bug.cgi?id=158471

Reviewed by Chris Dumez.

Source/WebCore:

Fixed the bug by calling the newly added ancestorElementInThisScope in webkitCurrentFullScreenElementForBindings
and webkitFullscreenElementForBinding.

The specification (https://fullscreen.spec.whatwg.org/#dom-document-fullscreenelement) uses "the result of
retargeting fullscreen element" and returns null if the result is not in the same tree as the context object.

This is equivalent to the algorithm implemented by ancestorElementInThisScope. Observe that the retargeting
algorithm (https://dom.spec.whatwg.org/#retarget) finds the lowest common tree scope of the retargetee and
the context object. There are two cases to consider.

1. The context object's tree scope is the lowest common tree scope: In this case, an ancestor shadow host or
the retargetee itself is in this tree scope. It's sufficient traverse every shadow host to find the one that
resides in the same tree scope as the context object. This is precisely what ancestorElementInThisScope does.

2. The context object's tree scope is not the lowest common tree scope: In this case, the context object is
inside a shadow tree whose ancestor shadow host is in the lowest common tree scope. In this case, retargeting
algorithm finds a node which is not in the same tree as the context object. Thus, the result is null.
ancestorElementInThisScope traveres ancestor shadow hosts and returns null if no shadow host's tree scope
matches that of the context object's tree scope. Thus, it would return null in this case as desired.

Also renamed TreeScope::focusedElement to focusedElementInScope for clarity since Document which inherits
from TreeScope also has a distinct member function named focusedElement called by TreeScope::focusedElement,
and used ancestorElementInThisScope since it uses the same algorithm.

Tests: fast/shadow-dom/activeElement-for-focused-element-in-another-shadow.html
       fast/shadow-dom/blur-on-shadow-host-with-focused-shadow-content.html
       fast/shadow-dom/fullscreen-in-shadow-fullscreenElement.html
       fast/shadow-dom/fullscreen-in-shadow-webkitCurrentFullScreenElement.html
       fast/shadow-dom/fullscreen-in-slot-fullscreenElement.html
       fast/shadow-dom/fullscreen-in-slot-webkitCurrentFullScreenElement.html

* dom/Document.cpp:
(WebCore::Document::removeFocusedNodeOfSubtree):
(WebCore::Document::activeElement):
* dom/Document.h:
(WebCore::Document::webkitCurrentFullScreenElementForBindings): Added.
(WebCore::Document::webkitFullscreenElementForBindings): Added.
* dom/Document.idl:
* dom/Element.cpp:
(WebCore::Element::blur):
* dom/ShadowRoot.h:
(WebCore::ShadowRoot::activeElement):
* dom/TreeScope.cpp:
(WebCore::TreeScope::ancestorNodeInThisScope): Renamed from ancestorInThisScope for clarity.
(WebCore::TreeScope::ancestorElementInThisScope):
(WebCore::TreeScope::focusedElementInScope): Renamed from focusedElement to disambiguate it from Document's
focusedElement.
* dom/TreeScope.h:
* editing/VisibleSelection.cpp:
(WebCore::adjustPositionForEnd):
(WebCore::adjustPositionForStart):
* editing/htmlediting.cpp:
(WebCore::comparePositions):
(WebCore::firstEditablePositionAfterPositionInRoot):
(WebCore::lastEditablePositionBeforePositionInRoot):
* page/DOMSelection.cpp:
(WebCore::selectionShadowAncestor):
(WebCore::DOMSelection::shadowAdjustedNode):
(WebCore::DOMSelection::shadowAdjustedOffset):
* rendering/HitTestResult.cpp:
(WebCore::HitTestResult::addNodeToRectBasedTestResult): Added a FIXME here since this is clearly wrong for
shadow trees created by author scripts.

Source/WebKit/mac:

Use the API for bindings to avoid exposing nodes inside a shadow tree.

* DOM/DOMDocument.mm:
(-[DOMDocument webkitCurrentFullScreenElement]):
(-[DOMDocument webkitFullscreenElement]):

LayoutTests:

Added tests for calling webkitFullscreenElement and webkitCurrentFullScreenElement on a fullscreened element
to make sure they return the shadow host instead.

Also added two unrelated test cases for temporal regressions I introduced while working on this patch.

Skip the fullscreen tests on iOS WK2 since eventSender doesn't work there.

* fast/shadow-dom/activeElement-for-focused-element-in-another-shadow-expected.txt: Added.
* fast/shadow-dom/activeElement-for-focused-element-in-another-shadow.html: Added.
* fast/shadow-dom/blur-on-shadow-host-with-focused-shadow-content-expected.txt: Added.
* fast/shadow-dom/blur-on-shadow-host-with-focused-shadow-content.html: Added.
* fast/shadow-dom/fullscreen-in-shadow-fullscreenElement-expected.txt: Added.
* fast/shadow-dom/fullscreen-in-shadow-fullscreenElement.html: Added.
* fast/shadow-dom/fullscreen-in-shadow-webkitCurrentFullScreenElement-expected.txt: Added.
* fast/shadow-dom/fullscreen-in-shadow-webkitCurrentFullScreenElement.html: Added.
* fast/shadow-dom/fullscreen-in-slot-fullscreenElement-expected.txt: Added.
* fast/shadow-dom/fullscreen-in-slot-fullscreenElement.html: Added.
* fast/shadow-dom/fullscreen-in-slot-webkitCurrentFullScreenElement-expected.txt: Added.
* fast/shadow-dom/fullscreen-in-slot-webkitCurrentFullScreenElement.html: Added.
* platform/ios-simulator-wk2/TestExpectations:

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (209627 => 209628)


--- trunk/LayoutTests/ChangeLog	2016-12-09 22:04:14 UTC (rev 209627)
+++ trunk/LayoutTests/ChangeLog	2016-12-09 22:06:29 UTC (rev 209628)
@@ -1,3 +1,31 @@
+2016-12-09  Ryosuke Niwa  <rn...@webkit.org>
+
+        document.webkitFullscreenElement leaks elements inside a shadow tree
+        https://bugs.webkit.org/show_bug.cgi?id=158471
+
+        Reviewed by Chris Dumez.
+
+        Added tests for calling webkitFullscreenElement and webkitCurrentFullScreenElement on a fullscreened element
+        to make sure they return the shadow host instead.
+
+        Also added two unrelated test cases for temporal regressions I introduced while working on this patch.
+
+        Skip the fullscreen tests on iOS WK2 since eventSender doesn't work there.
+
+        * fast/shadow-dom/activeElement-for-focused-element-in-another-shadow-expected.txt: Added.
+        * fast/shadow-dom/activeElement-for-focused-element-in-another-shadow.html: Added.
+        * fast/shadow-dom/blur-on-shadow-host-with-focused-shadow-content-expected.txt: Added.
+        * fast/shadow-dom/blur-on-shadow-host-with-focused-shadow-content.html: Added.
+        * fast/shadow-dom/fullscreen-in-shadow-fullscreenElement-expected.txt: Added.
+        * fast/shadow-dom/fullscreen-in-shadow-fullscreenElement.html: Added.
+        * fast/shadow-dom/fullscreen-in-shadow-webkitCurrentFullScreenElement-expected.txt: Added.
+        * fast/shadow-dom/fullscreen-in-shadow-webkitCurrentFullScreenElement.html: Added.
+        * fast/shadow-dom/fullscreen-in-slot-fullscreenElement-expected.txt: Added.
+        * fast/shadow-dom/fullscreen-in-slot-fullscreenElement.html: Added.
+        * fast/shadow-dom/fullscreen-in-slot-webkitCurrentFullScreenElement-expected.txt: Added.
+        * fast/shadow-dom/fullscreen-in-slot-webkitCurrentFullScreenElement.html: Added.
+        * platform/ios-simulator-wk2/TestExpectations:
+
 2016-12-09  Chris Dumez  <cdu...@apple.com>
 
         [Cocoa] Validation message for required checkbox doesn’t conform the the Apple Style Guide

Added: trunk/LayoutTests/fast/shadow-dom/activeElement-for-focused-element-in-another-shadow-expected.txt (0 => 209628)


--- trunk/LayoutTests/fast/shadow-dom/activeElement-for-focused-element-in-another-shadow-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/activeElement-for-focused-element-in-another-shadow-expected.txt	2016-12-09 22:06:29 UTC (rev 209628)
@@ -0,0 +1,4 @@
+
+PASS ShadowRoot.activeElement must return null the focused element is in another shadow tree when both shadow roots are open. 
+PASS ShadowRoot.activeElement must return null the focused element is in another shadow tree when both shadow roots are closed. 
+

Added: trunk/LayoutTests/fast/shadow-dom/activeElement-for-focused-element-in-another-shadow.html (0 => 209628)


--- trunk/LayoutTests/fast/shadow-dom/activeElement-for-focused-element-in-another-shadow.html	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/activeElement-for-focused-element-in-another-shadow.html	2016-12-09 22:06:29 UTC (rev 209628)
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta name="author" title="Ryosuke Niwa" href=""
+<meta name="assert" content="ShadowRoot's ">
+<link rel="help" href=""
+<script src=""
+<script src=""
+<script src=""
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+function testActiveElementWithFocusedElementInAnotherShadow(mode) {
+    test(function () {
+        const host = document.createElement('div');
+        document.body.appendChild(host);
+        const shadowRoot = host.attachShadow({'mode': mode});
+        const input = document.createElement('input');
+        shadowRoot.appendChild(input);
+        assert_equals(shadowRoot.activeElement, null);
+        input.focus();
+        assert_equals(shadowRoot.activeElement, input);
+        assert_equals(document.activeElement, host);
+
+        const anotherHost = document.createElement('div');
+        document.body.appendChild(anotherHost);
+        const anotherShadowRoot = anotherHost.attachShadow({'mode': mode});
+        const anotherInput = document.createElement('input');
+        anotherShadowRoot.appendChild(anotherInput);
+
+        assert_equals(anotherShadowRoot.activeElement, null);
+        assert_equals(shadowRoot.activeElement, input);
+        assert_equals(document.activeElement, host);
+
+        anotherInput.focus();
+        assert_equals(anotherShadowRoot.activeElement, anotherInput);
+        assert_equals(shadowRoot.activeElement, null);
+        assert_equals(document.activeElement, anotherHost);
+
+    }, 'ShadowRoot.activeElement must return null the focused element is in another shadow tree when both shadow roots are ' + mode + '.');
+}
+
+testActiveElementWithFocusedElementInAnotherShadow('open');
+testActiveElementWithFocusedElementInAnotherShadow('closed');
+
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/fast/shadow-dom/blur-on-shadow-host-with-focused-shadow-content-expected.txt (0 => 209628)


--- trunk/LayoutTests/fast/shadow-dom/blur-on-shadow-host-with-focused-shadow-content-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/blur-on-shadow-host-with-focused-shadow-content-expected.txt	2016-12-09 22:06:29 UTC (rev 209628)
@@ -0,0 +1,12 @@
+Test that calling blur() on a shadow host of a focused element clears the focus.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS input = shadowRoot.querySelector("input"); input.focus(); shadowRoot.activeElement is input
+PASS document.activeElement is shadowHost
+PASS shadowHost.blur(); shadowRoot.activeElement is null
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/fast/shadow-dom/blur-on-shadow-host-with-focused-shadow-content.html (0 => 209628)


--- trunk/LayoutTests/fast/shadow-dom/blur-on-shadow-host-with-focused-shadow-content.html	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/blur-on-shadow-host-with-focused-shadow-content.html	2016-12-09 22:06:29 UTC (rev 209628)
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div id="host"></div>
+<script src=""
+<script>
+
+description('Test that calling blur() on a shadow host of a focused element clears the focus.');
+
+let shadowHost = document.getElementById('host');
+let shadowRoot = shadowHost.attachShadow({mode: 'closed'});
+shadowRoot.innerHTML = `<input>`;
+
+shouldBe('input = shadowRoot.querySelector("input"); input.focus(); shadowRoot.activeElement', 'input');
+shouldBe('document.activeElement', 'shadowHost');
+shouldBe('shadowHost.blur(); shadowRoot.activeElement', 'null');
+
+var successfullyParsed = true;
+
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/fast/shadow-dom/fullscreen-in-shadow-fullscreenElement-expected.txt (0 => 209628)


--- trunk/LayoutTests/fast/shadow-dom/fullscreen-in-shadow-fullscreenElement-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/fullscreen-in-shadow-fullscreenElement-expected.txt	2016-12-09 22:06:29 UTC (rev 209628)
@@ -0,0 +1,10 @@
+Test that webkitFullscreenElement retargets the fullscreen element inside a shadow tree.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS document.webkitFullscreenElement is shadowHost
+PASS successfullyParsed is true
+
+TEST COMPLETE
+Enter fullscreen

Added: trunk/LayoutTests/fast/shadow-dom/fullscreen-in-shadow-fullscreenElement.html (0 => 209628)


--- trunk/LayoutTests/fast/shadow-dom/fullscreen-in-shadow-fullscreenElement.html	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/fullscreen-in-shadow-fullscreenElement.html	2016-12-09 22:06:29 UTC (rev 209628)
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div id="host"></div>
+<button>Enter fullscreen</button>
+<script src=""
+<script>
+
+description('Test that webkitFullscreenElement retargets the fullscreen element inside a shadow tree.');
+
+let shadowHost = document.getElementById('host');
+let shadowRoot = shadowHost.attachShadow({mode: 'closed'});
+shadowRoot.innerHTML = '<span>full screen content</span>';
+
+function goFullscreen() {
+    shadowRoot.querySelector('span').webkitRequestFullscreen();
+    setTimeout(function () {
+        if (done)
+            return;
+
+        testFailed('webkitfullscreenchange was not fired');
+        finishJSTest();
+    }, 2000);
+}
+
+let done = false;
+function finalizeTest() {
+    if (done)
+        return;
+    done = true;
+
+    shouldBe('document.webkitFullscreenElement', 'shadowHost');
+    finishJSTest();
+}
+
+shadowRoot.addEventListener('webkitfullscreenchange', finalizeTest);
+document.addEventListener('fullscreenchange', finalizeTest); // Standard fullscreenchange only fires at document level.
+
+let button = document.querySelector('button');
+button._onclick_ = goFullscreen;
+
+if (window.eventSender) {
+    jsTestIsAsync = true;
+    eventSender.mouseMoveTo(button.offsetLeft + 5, button.offsetTop + 5);
+    eventSender.mouseDown();
+    eventSender.mouseUp();
+}
+
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/fast/shadow-dom/fullscreen-in-shadow-webkitCurrentFullScreenElement-expected.txt (0 => 209628)


--- trunk/LayoutTests/fast/shadow-dom/fullscreen-in-shadow-webkitCurrentFullScreenElement-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/fullscreen-in-shadow-webkitCurrentFullScreenElement-expected.txt	2016-12-09 22:06:29 UTC (rev 209628)
@@ -0,0 +1,10 @@
+Test that webkitCurrentFullScreenElement retargets the fullscreen element inside a shadow tree.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS document.webkitCurrentFullScreenElement is shadowHost
+PASS successfullyParsed is true
+
+TEST COMPLETE
+Enter fullscreen

Added: trunk/LayoutTests/fast/shadow-dom/fullscreen-in-shadow-webkitCurrentFullScreenElement.html (0 => 209628)


--- trunk/LayoutTests/fast/shadow-dom/fullscreen-in-shadow-webkitCurrentFullScreenElement.html	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/fullscreen-in-shadow-webkitCurrentFullScreenElement.html	2016-12-09 22:06:29 UTC (rev 209628)
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div id="host"></div>
+<button>Enter fullscreen</button>
+<script src=""
+<script>
+
+description('Test that webkitCurrentFullScreenElement retargets the fullscreen element inside a shadow tree.');
+
+let shadowHost = document.getElementById('host');
+let shadowRoot = shadowHost.attachShadow({mode: 'closed'});
+shadowRoot.innerHTML = '<span>full screen content</span>';
+
+function goFullscreen() {
+    shadowRoot.querySelector('span').webkitRequestFullScreen();
+    setTimeout(function () {
+        if (done)
+            return;
+
+        testFailed('webkitfullscreenchange was not fired');
+        finishJSTest();
+    }, 2000);
+}
+
+let done = false;
+function finalizeTest() {
+    if (done)
+        return;
+    done = true;
+
+    shouldBe('document.webkitCurrentFullScreenElement', 'shadowHost');
+    finishJSTest();
+}
+
+shadowRoot.addEventListener('webkitfullscreenchange', finalizeTest);
+document.addEventListener('fullscreenchange', finalizeTest); // Standard fullscreenchange only fires at document level.
+
+let button = document.querySelector('button');
+button._onclick_ = goFullscreen;
+
+if (window.eventSender) {
+    jsTestIsAsync = true;
+    eventSender.mouseMoveTo(button.offsetLeft + 5, button.offsetTop + 5);
+    eventSender.mouseDown();
+    eventSender.mouseUp();
+}
+
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/fast/shadow-dom/fullscreen-in-slot-fullscreenElement-expected.txt (0 => 209628)


--- trunk/LayoutTests/fast/shadow-dom/fullscreen-in-slot-fullscreenElement-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/fullscreen-in-slot-fullscreenElement-expected.txt	2016-12-09 22:06:29 UTC (rev 209628)
@@ -0,0 +1,11 @@
+Test that webkitFullscreenElement retargets the fullscreen element assigned to a slot.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS document.webkitFullscreenElement is document.querySelector("section")
+PASS successfullyParsed is true
+
+TEST COMPLETE
+full screen content
+Enter fullscreen

Added: trunk/LayoutTests/fast/shadow-dom/fullscreen-in-slot-fullscreenElement.html (0 => 209628)


--- trunk/LayoutTests/fast/shadow-dom/fullscreen-in-slot-fullscreenElement.html	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/fullscreen-in-slot-fullscreenElement.html	2016-12-09 22:06:29 UTC (rev 209628)
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div id="host"><section>full screen content</section></div>
+<button>Enter fullscreen</button>
+<script src=""
+<script>
+
+description('Test that webkitFullscreenElement retargets the fullscreen element assigned to a slot.');
+
+let shadowHost = document.getElementById('host');
+let shadowRoot = shadowHost.attachShadow({mode: 'closed'});
+shadowRoot.innerHTML = '<span><slot></slot></span>';
+
+function goFullscreen() {
+    document.querySelector('section').webkitRequestFullscreen();
+    setTimeout(function () {
+        if (done)
+            return;
+
+        testFailed('webkitfullscreenchange was not fired');
+        finishJSTest();
+    }, 2000);
+}
+
+let done = false;
+function finalizeTest() {
+    if (done)
+        return;
+    done = true;
+
+    shouldBe('document.webkitFullscreenElement', 'document.querySelector("section")');
+    finishJSTest();
+}
+
+shadowRoot.addEventListener('webkitfullscreenchange', finalizeTest);
+document.addEventListener('fullscreenchange', finalizeTest); // Standard fullscreenchange only fires at document level.
+
+let button = document.querySelector('button');
+button._onclick_ = goFullscreen;
+
+if (window.eventSender) {
+    jsTestIsAsync = true;
+    eventSender.mouseMoveTo(button.offsetLeft + 5, button.offsetTop + 5);
+    eventSender.mouseDown();
+    eventSender.mouseUp();
+}
+
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/fast/shadow-dom/fullscreen-in-slot-webkitCurrentFullScreenElement-expected.txt (0 => 209628)


--- trunk/LayoutTests/fast/shadow-dom/fullscreen-in-slot-webkitCurrentFullScreenElement-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/fullscreen-in-slot-webkitCurrentFullScreenElement-expected.txt	2016-12-09 22:06:29 UTC (rev 209628)
@@ -0,0 +1,11 @@
+Test that webkitCurrentFullScreenElement retargets the fullscreen element assigned to a slot.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS document.webkitCurrentFullScreenElement is document.querySelector("section")
+PASS successfullyParsed is true
+
+TEST COMPLETE
+full screen content
+Enter fullscreen

Added: trunk/LayoutTests/fast/shadow-dom/fullscreen-in-slot-webkitCurrentFullScreenElement.html (0 => 209628)


--- trunk/LayoutTests/fast/shadow-dom/fullscreen-in-slot-webkitCurrentFullScreenElement.html	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/fullscreen-in-slot-webkitCurrentFullScreenElement.html	2016-12-09 22:06:29 UTC (rev 209628)
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div id="host"><section>full screen content</section></div>
+<button>Enter fullscreen</button>
+<script src=""
+<script>
+
+description('Test that webkitCurrentFullScreenElement retargets the fullscreen element assigned to a slot.');
+
+let shadowHost = document.getElementById('host');
+let shadowRoot = shadowHost.attachShadow({mode: 'closed'});
+shadowRoot.innerHTML = '<span><slot></slot></span>';
+
+function goFullscreen() {
+    document.querySelector('section').webkitRequestFullScreen();
+    setTimeout(function () {
+        if (done)
+            return;
+
+        testFailed('webkitfullscreenchange was not fired');
+        finishJSTest();
+    }, 2000);
+}
+
+let done = false;
+function finalizeTest() {
+    if (done)
+        return;
+    done = true;
+
+    shouldBe('document.webkitCurrentFullScreenElement', 'document.querySelector("section")');
+    finishJSTest();
+}
+
+shadowRoot.addEventListener('webkitfullscreenchange', finalizeTest);
+document.addEventListener('fullscreenchange', finalizeTest); // Standard fullscreenchange only fires at document level.
+
+let button = document.querySelector('button');
+button._onclick_ = goFullscreen;
+
+if (window.eventSender) {
+    jsTestIsAsync = true;
+    eventSender.mouseMoveTo(button.offsetLeft + 5, button.offsetTop + 5);
+    eventSender.mouseDown();
+    eventSender.mouseUp();
+}
+
+</script>
+</body>
+</html>

Modified: trunk/LayoutTests/platform/ios-simulator-wk2/TestExpectations (209627 => 209628)


--- trunk/LayoutTests/platform/ios-simulator-wk2/TestExpectations	2016-12-09 22:04:14 UTC (rev 209627)
+++ trunk/LayoutTests/platform/ios-simulator-wk2/TestExpectations	2016-12-09 22:06:29 UTC (rev 209628)
@@ -1778,6 +1778,10 @@
 imported/blink/editing/selection/selectstart-event-crash.html [ Skip ]
 fast/dom/Window/post-message-user-action.html [ Skip ]
 fast/shadow-dom/click-text-inside-linked-slot.html [ Skip ]
+fast/shadow-dom/fullscreen-in-shadow-fullscreenElement.html
+fast/shadow-dom/fullscreen-in-shadow-webkitCurrentFullScreenElement.html
+fast/shadow-dom/fullscreen-in-slot-fullscreenElement.html
+fast/shadow-dom/fullscreen-in-slot-webkitCurrentFullScreenElement.html
 
 # No touch events
 http/tests/contentdispositionattachmentsandbox/referer-header-stripped-with-meta-referer-always.html [ Skip ]

Modified: trunk/Source/WebCore/ChangeLog (209627 => 209628)


--- trunk/Source/WebCore/ChangeLog	2016-12-09 22:04:14 UTC (rev 209627)
+++ trunk/Source/WebCore/ChangeLog	2016-12-09 22:06:29 UTC (rev 209628)
@@ -1,3 +1,73 @@
+2016-12-09  Ryosuke Niwa  <rn...@webkit.org>
+
+        document.webkitFullscreenElement leaks elements inside a shadow tree
+        https://bugs.webkit.org/show_bug.cgi?id=158471
+
+        Reviewed by Chris Dumez.
+
+        Fixed the bug by calling the newly added ancestorElementInThisScope in webkitCurrentFullScreenElementForBindings
+        and webkitFullscreenElementForBinding.
+
+        The specification (https://fullscreen.spec.whatwg.org/#dom-document-fullscreenelement) uses "the result of
+        retargeting fullscreen element" and returns null if the result is not in the same tree as the context object.
+
+        This is equivalent to the algorithm implemented by ancestorElementInThisScope. Observe that the retargeting
+        algorithm (https://dom.spec.whatwg.org/#retarget) finds the lowest common tree scope of the retargetee and
+        the context object. There are two cases to consider.
+
+        1. The context object's tree scope is the lowest common tree scope: In this case, an ancestor shadow host or
+        the retargetee itself is in this tree scope. It's sufficient traverse every shadow host to find the one that
+        resides in the same tree scope as the context object. This is precisely what ancestorElementInThisScope does.
+
+        2. The context object's tree scope is not the lowest common tree scope: In this case, the context object is
+        inside a shadow tree whose ancestor shadow host is in the lowest common tree scope. In this case, retargeting
+        algorithm finds a node which is not in the same tree as the context object. Thus, the result is null.
+        ancestorElementInThisScope traveres ancestor shadow hosts and returns null if no shadow host's tree scope
+        matches that of the context object's tree scope. Thus, it would return null in this case as desired.
+
+        Also renamed TreeScope::focusedElement to focusedElementInScope for clarity since Document which inherits
+        from TreeScope also has a distinct member function named focusedElement called by TreeScope::focusedElement,
+        and used ancestorElementInThisScope since it uses the same algorithm.
+
+        Tests: fast/shadow-dom/activeElement-for-focused-element-in-another-shadow.html
+               fast/shadow-dom/blur-on-shadow-host-with-focused-shadow-content.html
+               fast/shadow-dom/fullscreen-in-shadow-fullscreenElement.html
+               fast/shadow-dom/fullscreen-in-shadow-webkitCurrentFullScreenElement.html
+               fast/shadow-dom/fullscreen-in-slot-fullscreenElement.html
+               fast/shadow-dom/fullscreen-in-slot-webkitCurrentFullScreenElement.html
+
+        * dom/Document.cpp:
+        (WebCore::Document::removeFocusedNodeOfSubtree):
+        (WebCore::Document::activeElement):
+        * dom/Document.h:
+        (WebCore::Document::webkitCurrentFullScreenElementForBindings): Added.
+        (WebCore::Document::webkitFullscreenElementForBindings): Added.
+        * dom/Document.idl:
+        * dom/Element.cpp:
+        (WebCore::Element::blur):
+        * dom/ShadowRoot.h:
+        (WebCore::ShadowRoot::activeElement):
+        * dom/TreeScope.cpp:
+        (WebCore::TreeScope::ancestorNodeInThisScope): Renamed from ancestorInThisScope for clarity.
+        (WebCore::TreeScope::ancestorElementInThisScope):
+        (WebCore::TreeScope::focusedElementInScope): Renamed from focusedElement to disambiguate it from Document's
+        focusedElement.
+        * dom/TreeScope.h:
+        * editing/VisibleSelection.cpp:
+        (WebCore::adjustPositionForEnd):
+        (WebCore::adjustPositionForStart):
+        * editing/htmlediting.cpp:
+        (WebCore::comparePositions):
+        (WebCore::firstEditablePositionAfterPositionInRoot):
+        (WebCore::lastEditablePositionBeforePositionInRoot):
+        * page/DOMSelection.cpp:
+        (WebCore::selectionShadowAncestor):
+        (WebCore::DOMSelection::shadowAdjustedNode):
+        (WebCore::DOMSelection::shadowAdjustedOffset):
+        * rendering/HitTestResult.cpp:
+        (WebCore::HitTestResult::addNodeToRectBasedTestResult): Added a FIXME here since this is clearly wrong for
+        shadow trees created by author scripts.
+
 2016-12-09  Geoffrey Garen  <gga...@apple.com>
 
         TextPosition and OrdinalNumber should be more like idiomatic numbers

Modified: trunk/Source/WebCore/dom/Document.cpp (209627 => 209628)


--- trunk/Source/WebCore/dom/Document.cpp	2016-12-09 22:04:14 UTC (rev 209627)
+++ trunk/Source/WebCore/dom/Document.cpp	2016-12-09 22:06:29 UTC (rev 209628)
@@ -3540,7 +3540,7 @@
     if (!m_focusedElement || pageCacheState() != NotInPageCache) // If the document is in the page cache, then we don't need to clear out the focused node.
         return;
 
-    Element* focusedElement = node.treeScope().focusedElement();
+    Element* focusedElement = node.treeScope().focusedElementInScope();
     if (!focusedElement)
         return;
     
@@ -6771,7 +6771,7 @@
 
 Element* Document::activeElement()
 {
-    if (Element* element = treeScope().focusedElement())
+    if (Element* element = treeScope().focusedElementInScope())
         return element;
     return bodyOrFrameset();
 }

Modified: trunk/Source/WebCore/dom/Document.h (209627 => 209628)


--- trunk/Source/WebCore/dom/Document.h	2016-12-09 22:04:14 UTC (rev 209627)
+++ trunk/Source/WebCore/dom/Document.h	2016-12-09 22:06:29 UTC (rev 209628)
@@ -1088,7 +1088,8 @@
     bool webkitIsFullScreen() const { return m_fullScreenElement.get(); }
     bool webkitFullScreenKeyboardInputAllowed() const { return m_fullScreenElement.get() && m_areKeysEnabledInFullScreen; }
     Element* webkitCurrentFullScreenElement() const { return m_fullScreenElement.get(); }
-    
+    Element* webkitCurrentFullScreenElementForBindings() const { return ancestorElementInThisScope(webkitCurrentFullScreenElement()); }
+
     enum FullScreenCheckType {
         EnforceIFrameAllowFullScreenRequirement,
         ExemptIFrameAllowFullScreenRequirement,
@@ -1115,6 +1116,7 @@
 
     WEBCORE_EXPORT bool webkitFullscreenEnabled() const;
     Element* webkitFullscreenElement() const { return !m_fullScreenElementStack.isEmpty() ? m_fullScreenElementStack.last().get() : nullptr; }
+    Element* webkitFullscreenElementForBindings() const { return ancestorElementInThisScope(webkitFullscreenElement()); }
     WEBCORE_EXPORT void webkitExitFullscreen();
 #endif
 

Modified: trunk/Source/WebCore/dom/Document.idl (209627 => 209628)


--- trunk/Source/WebCore/dom/Document.idl	2016-12-09 22:04:14 UTC (rev 209627)
+++ trunk/Source/WebCore/dom/Document.idl	2016-12-09 22:06:29 UTC (rev 209628)
@@ -143,12 +143,12 @@
     // Mozilla version
     readonly attribute boolean webkitIsFullScreen;
     readonly attribute boolean webkitFullScreenKeyboardInputAllowed;
-    readonly attribute Element webkitCurrentFullScreenElement;
+    [ImplementedAs=webkitCurrentFullScreenElementForBindings] readonly attribute Element webkitCurrentFullScreenElement;
     void webkitCancelFullScreen();
 
     // W3C version
     readonly attribute boolean webkitFullscreenEnabled;
-    readonly attribute Element? webkitFullscreenElement;
+    [ImplementedAs=webkitFullscreenElementForBindings] readonly attribute Element? webkitFullscreenElement;
     void webkitExitFullscreen();
 #endif
 

Modified: trunk/Source/WebCore/dom/Element.cpp (209627 => 209628)


--- trunk/Source/WebCore/dom/Element.cpp	2016-12-09 22:04:14 UTC (rev 209627)
+++ trunk/Source/WebCore/dom/Element.cpp	2016-12-09 22:06:29 UTC (rev 209628)
@@ -2438,7 +2438,7 @@
 void Element::blur()
 {
     cancelFocusAppearanceUpdate();
-    if (treeScope().focusedElement() == this) {
+    if (treeScope().focusedElementInScope() == this) {
         if (Frame* frame = document().frame())
             frame->page()->focusController().setFocusedElement(0, frame);
         else

Modified: trunk/Source/WebCore/dom/ShadowRoot.h (209627 => 209628)


--- trunk/Source/WebCore/dom/ShadowRoot.h	2016-12-09 22:04:14 UTC (rev 209627)
+++ trunk/Source/WebCore/dom/ShadowRoot.h	2016-12-09 22:06:29 UTC (rev 209628)
@@ -110,7 +110,7 @@
 
 inline Element* ShadowRoot::activeElement() const
 {
-    return treeScope().focusedElement();
+    return treeScope().focusedElementInScope();
 }
 
 inline ShadowRoot* Node::shadowRoot() const

Modified: trunk/Source/WebCore/dom/TreeScope.cpp (209627 => 209628)


--- trunk/Source/WebCore/dom/TreeScope.cpp	2016-12-09 22:04:14 UTC (rev 209627)
+++ trunk/Source/WebCore/dom/TreeScope.cpp	2016-12-09 22:06:29 UTC (rev 209628)
@@ -195,7 +195,7 @@
     return *shadowRootInLowestCommonTreeScope.host();
 }
 
-Node* TreeScope::ancestorInThisScope(Node* node) const
+Node* TreeScope::ancestorNodeInThisScope(Node* node) const
 {
     for (; node; node = node->shadowHost()) {
         if (&node->treeScope() == this)
@@ -206,6 +206,17 @@
     return nullptr;
 }
 
+Element* TreeScope::ancestorElementInThisScope(Element* element) const
+{
+    for (; element; element = element->shadowHost()) {
+        if (&element->treeScope() == this)
+            return element;
+        if (!element->isInShadowTree())
+            return nullptr;
+    }
+    return nullptr;
+}
+
 void TreeScope::addImageMap(HTMLMapElement& imageMap)
 {
     AtomicStringImpl* name = imageMap.getName().impl();
@@ -364,7 +375,7 @@
     return nullptr;
 }
 
-Element* TreeScope::focusedElement()
+Element* TreeScope::focusedElementInScope()
 {
     Document& document = m_rootNode.document();
     Element* element = document.focusedElement();
@@ -371,21 +382,8 @@
 
     if (!element && document.page())
         element = focusedFrameOwnerElement(document.page()->focusController().focusedFrame(), document.frame());
-    if (!element)
-        return nullptr;
-    TreeScope* treeScope = &element->treeScope();
-    RELEASE_ASSERT(&document == &treeScope->documentScope());
-    while (treeScope != this && treeScope != &document) {
-        auto& rootNode = treeScope->rootNode();
-        if (is<ShadowRoot>(rootNode))
-            element = downcast<ShadowRoot>(rootNode).host();
-        else
-            return nullptr;
-        treeScope = &element->treeScope();
-    }
-    if (this != treeScope)
-        return nullptr;
-    return element;
+
+    return ancestorElementInThisScope(element);
 }
 
 static void listTreeScopes(Node* node, Vector<TreeScope*, 5>& treeScopes)

Modified: trunk/Source/WebCore/dom/TreeScope.h (209627 => 209628)


--- trunk/Source/WebCore/dom/TreeScope.h	2016-12-09 22:04:14 UTC (rev 209627)
+++ trunk/Source/WebCore/dom/TreeScope.h	2016-12-09 22:06:29 UTC (rev 209628)
@@ -51,7 +51,7 @@
     TreeScope* parentTreeScope() const { return m_parentTreeScope; }
     void setParentTreeScope(TreeScope&);
 
-    Element* focusedElement();
+    Element* focusedElementInScope();
     WEBCORE_EXPORT Element* getElementById(const AtomicString&) const;
     WEBCORE_EXPORT Element* getElementById(const String&) const;
     const Vector<Element*>* getAllElementsById(const AtomicString&) const;
@@ -72,7 +72,8 @@
     // https://dom.spec.whatwg.org/#retarget
     Node& retargetToScope(Node&) const;
 
-    Node* ancestorInThisScope(Node*) const;
+    Node* ancestorNodeInThisScope(Node*) const;
+    WEBCORE_EXPORT Element* ancestorElementInThisScope(Element*) const;
 
     void addImageMap(HTMLMapElement&);
     void removeImageMap(HTMLMapElement&);

Modified: trunk/Source/WebCore/editing/VisibleSelection.cpp (209627 => 209628)


--- trunk/Source/WebCore/editing/VisibleSelection.cpp	2016-12-09 22:04:14 UTC (rev 209627)
+++ trunk/Source/WebCore/editing/VisibleSelection.cpp	2016-12-09 22:06:29 UTC (rev 209628)
@@ -473,7 +473,7 @@
 
     ASSERT(&currentPosition.containerNode()->treeScope() != &treeScope);
 
-    if (Node* ancestor = treeScope.ancestorInThisScope(currentPosition.containerNode())) {
+    if (Node* ancestor = treeScope.ancestorNodeInThisScope(currentPosition.containerNode())) {
         if (ancestor->contains(startContainerNode))
             return positionAfterNode(ancestor);
         return positionBeforeNode(ancestor);
@@ -491,7 +491,7 @@
 
     ASSERT(&currentPosition.containerNode()->treeScope() != &treeScope);
     
-    if (Node* ancestor = treeScope.ancestorInThisScope(currentPosition.containerNode())) {
+    if (Node* ancestor = treeScope.ancestorNodeInThisScope(currentPosition.containerNode())) {
         if (ancestor->contains(endContainerNode))
             return positionBeforeNode(ancestor);
         return positionAfterNode(ancestor);

Modified: trunk/Source/WebCore/editing/htmlediting.cpp (209627 => 209628)


--- trunk/Source/WebCore/editing/htmlediting.cpp	2016-12-09 22:04:14 UTC (rev 209627)
+++ trunk/Source/WebCore/editing/htmlediting.cpp	2016-12-09 22:06:29 UTC (rev 209628)
@@ -83,12 +83,12 @@
     if (!commonScope)
         return 0;
 
-    Node* nodeA = commonScope->ancestorInThisScope(a.containerNode());
+    Node* nodeA = commonScope->ancestorNodeInThisScope(a.containerNode());
     ASSERT(nodeA);
     bool hasDescendentA = nodeA != a.containerNode();
     int offsetA = hasDescendentA ? 0 : a.computeOffsetInContainerNode();
 
-    Node* nodeB = commonScope->ancestorInThisScope(b.containerNode());
+    Node* nodeB = commonScope->ancestorNodeInThisScope(b.containerNode());
     ASSERT(nodeB);
     bool hasDescendentB = nodeB != b.containerNode();
     int offsetB = hasDescendentB ? 0 : b.computeOffsetInContainerNode();
@@ -292,7 +292,7 @@
     Position candidate = position;
 
     if (&position.deprecatedNode()->treeScope() != &highestRoot->treeScope()) {
-        auto* shadowAncestor = highestRoot->treeScope().ancestorInThisScope(position.deprecatedNode());
+        auto* shadowAncestor = highestRoot->treeScope().ancestorNodeInThisScope(position.deprecatedNode());
         if (!shadowAncestor)
             return { };
 
@@ -320,7 +320,7 @@
     Position candidate = position;
 
     if (&position.deprecatedNode()->treeScope() != &highestRoot->treeScope()) {
-        auto* shadowAncestor = highestRoot->treeScope().ancestorInThisScope(position.deprecatedNode());
+        auto* shadowAncestor = highestRoot->treeScope().ancestorNodeInThisScope(position.deprecatedNode());
         if (!shadowAncestor)
             return { };
 

Modified: trunk/Source/WebCore/page/DOMSelection.cpp (209627 => 209628)


--- trunk/Source/WebCore/page/DOMSelection.cpp	2016-12-09 22:04:14 UTC (rev 209627)
+++ trunk/Source/WebCore/page/DOMSelection.cpp	2016-12-09 22:06:29 UTC (rev 209628)
@@ -49,7 +49,7 @@
     if (!node->isInShadowTree())
         return nullptr;
     // FIXME: Unclear on why this needs to be the possibly null frame.document() instead of the never null node->document().
-    return frame.document()->ancestorInThisScope(node);
+    return frame.document()->ancestorNodeInThisScope(node);
 }
 
 DOMSelection::DOMSelection(Frame& frame)
@@ -436,7 +436,7 @@
         return nullptr;
 
     auto* containerNode = position.containerNode();
-    auto* adjustedNode = m_frame->document()->ancestorInThisScope(containerNode);
+    auto* adjustedNode = m_frame->document()->ancestorNodeInThisScope(containerNode);
     if (!adjustedNode)
         return nullptr;
 
@@ -452,7 +452,7 @@
         return 0;
 
     auto* containerNode = position.containerNode();
-    auto* adjustedNode = m_frame->document()->ancestorInThisScope(containerNode);
+    auto* adjustedNode = m_frame->document()->ancestorNodeInThisScope(containerNode);
     if (!adjustedNode)
         return 0;
 

Modified: trunk/Source/WebCore/rendering/HitTestResult.cpp (209627 => 209628)


--- trunk/Source/WebCore/rendering/HitTestResult.cpp	2016-12-09 22:04:14 UTC (rev 209627)
+++ trunk/Source/WebCore/rendering/HitTestResult.cpp	2016-12-09 22:06:29 UTC (rev 209628)
@@ -668,8 +668,9 @@
     if (!node)
         return true;
 
+    // FIXME: This moves out of a author shadow tree.
     if (request.disallowsUserAgentShadowContent())
-        node = node->document().ancestorInThisScope(node);
+        node = node->document().ancestorNodeInThisScope(node);
 
     mutableRectBasedTestResult().add(node);
 
@@ -688,8 +689,9 @@
     if (!node)
         return true;
 
+    // FIXME: This moves out of a author shadow tree.
     if (request.disallowsUserAgentShadowContent())
-        node = node->document().ancestorInThisScope(node);
+        node = node->document().ancestorNodeInThisScope(node);
 
     mutableRectBasedTestResult().add(node);
 

Modified: trunk/Source/WebKit/mac/ChangeLog (209627 => 209628)


--- trunk/Source/WebKit/mac/ChangeLog	2016-12-09 22:04:14 UTC (rev 209627)
+++ trunk/Source/WebKit/mac/ChangeLog	2016-12-09 22:06:29 UTC (rev 209628)
@@ -1,3 +1,16 @@
+2016-12-09  Ryosuke Niwa  <rn...@webkit.org>
+
+        document.webkitFullscreenElement leaks elements inside a shadow tree
+        https://bugs.webkit.org/show_bug.cgi?id=158471
+
+        Reviewed by Chris Dumez.
+
+        Use the API for bindings to avoid exposing nodes inside a shadow tree.
+
+        * DOM/DOMDocument.mm:
+        (-[DOMDocument webkitCurrentFullScreenElement]):
+        (-[DOMDocument webkitFullscreenElement]):
+
 2016-12-09  Beth Dakin  <bda...@apple.com>
 
         Password fields should not show the emoji button in TouchBar

Modified: trunk/Source/WebKit/mac/DOM/DOMDocument.mm (209627 => 209628)


--- trunk/Source/WebKit/mac/DOM/DOMDocument.mm	2016-12-09 22:04:14 UTC (rev 209627)
+++ trunk/Source/WebKit/mac/DOM/DOMDocument.mm	2016-12-09 22:06:29 UTC (rev 209628)
@@ -361,7 +361,7 @@
 - (DOMElement *)webkitCurrentFullScreenElement
 {
     WebCore::JSMainThreadNullState state;
-    return kit(WTF::getPtr(IMPL->webkitCurrentFullScreenElement()));
+    return kit(WTF::getPtr(IMPL->webkitCurrentFullScreenElementForBindings()));
 }
 
 - (BOOL)webkitFullscreenEnabled
@@ -373,7 +373,7 @@
 - (DOMElement *)webkitFullscreenElement
 {
     WebCore::JSMainThreadNullState state;
-    return kit(WTF::getPtr(IMPL->webkitFullscreenElement()));
+    return kit(WTF::getPtr(IMPL->webkitFullscreenElementForBindings()));
 }
 
 #endif
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to