Title: [210432] trunk
Revision
210432
Author
rn...@webkit.org
Date
2017-01-05 19:45:16 -0800 (Thu, 05 Jan 2017)

Log Message

Finding text doesn't work across shadow boundary
https://bugs.webkit.org/show_bug.cgi?id=158503

Reviewed by Antti Koivisto.

Source/WebCore:

Added a new TextIterator behavior flag, TextIteratorTraversesFlatTree, which makes TextIterator traverse
the flat tree instead of the DOM tree, and made this behavior default in findPlainText.

Also added a new find options flag, DoNotTraverseFlatTree, to suppress this behavior in window.find(~)
and execCommand('FindString', false, ~) as they should not be able to peek information inside shadow trees.
Unfortunately these APIs have been deprecated in the standards so there is no specification to follow.

For now, we don't support finding a word or a keyword across a shadow boundary as this would require
making rangeOfString and other related functions return a Range-like object that can cross shadow boundaries.

Also added internals.rangeOfString to test Editor::rangeOfString, and replaced the bit-flag arguments
to internals.countMatchesForText and internals.countFindMatches by an array of strings for better portability.

Test: editing/text-iterator/find-string-on-flat-tree.html

* editing/Editor.cpp:
(WebCore::Editor::rangeOfString): Use the modern containingShadowRoot instead of nonBoundaryShadowTreeRootNode
since the start container can be a shadow root, which nonBoundaryShadowTreeRootNode asserts not be the case.
* editing/Editor.h:
* editing/EditorCommand.cpp:
(WebCore::executeFindString): Don't traverse across shadow boundaries.
* editing/FindOptions.h: Added DoNotTraverseFlatTree.
* editing/TextIterator.cpp:
(WebCore::assignedAuthorSlot): Added.
(WebCore::authorShadowRoot): Added.
(WebCore::firstChildInFlatTreeIgnoringUserAgentShadow): Added.
(WebCore::nextSiblingInFlatTreeIgnoringUserAgentShadow): Added.
(WebCore::firstChild): Added. Traverses the flat tree when TextIteratorTraversesFlatTree is set.
(WebCore::nextSibling): Ditto.
(WebCore::parentNodeOrShadowHost): Ditto.
(WebCore::TextIterator::advance): Don't set m_handledChildren to true when the current node has display: contents.
(WebCore::findPlainText): Use TextIteratorTraversesFlatTree unless DoNotTraverseFlatTree is set.
* editing/TextIteratorBehavior.h: Added TextIteratorTraversesFlatTree.
* page/DOMWindow.cpp:
(WebCore::DOMWindow::find): Don't traverse across shadow boundaries.
* testing/Internals.cpp:
(WebCore::parseFindOptions): Added.
(WebCore::Internals::rangeOfString): Added.
(WebCore::Internals::countMatchesForText): Replaced the find options by an array of strings instead of a bit mask.
(WebCore::Internals::countFindMatches): Ditto.
* testing/Internals.h:
* testing/Internals.idl: Added rangeOfString, and replaced find options bit-flag in countMatchesForText and
countFindMatches by an array of strings so that the tests themselves don't rely on a specific value of each bit flag.

LayoutTests:

Updated the existing tests per changes to use an array of find options instead of raw bit mask,
and added a regression test for finding text by traversing flat tree along with testing
window.find and execCommand('FindString', false, ~) not walking across shadow boundaries.

* editing/text-iterator/count-mark-lineboxes-expected.txt:
* editing/text-iterator/count-mark-lineboxes.html:
* editing/text-iterator/count-mark-simple-lines-expected.txt:
* editing/text-iterator/count-mark-simple-lines.html:
* editing/text-iterator/count-matches-in-form-expected.txt:
* editing/text-iterator/count-matches-in-form.html:
* editing/text-iterator/count-matches-in-frames.html:
* editing/text-iterator/find-string-on-flat-tree-expected.txt: Added.
* editing/text-iterator/find-string-on-flat-tree.html: Added.
* fast/text/mark-matches-broken-line-rendering.html:
* fast/text/mark-matches-overflow-clip.html:
* fast/text/mark-matches-rendering-simple-lines-expected.html:
* fast/text/mark-matches-rendering-simple-lines.html:
* fast/text/mark-matches-rendering.html:

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (210431 => 210432)


--- trunk/LayoutTests/ChangeLog	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/LayoutTests/ChangeLog	2017-01-06 03:45:16 UTC (rev 210432)
@@ -1,3 +1,29 @@
+2017-01-05  Ryosuke Niwa  <rn...@webkit.org>
+
+        Finding text doesn't work across shadow boundary
+        https://bugs.webkit.org/show_bug.cgi?id=158503
+
+        Reviewed by Antti Koivisto.
+
+        Updated the existing tests per changes to use an array of find options instead of raw bit mask,
+        and added a regression test for finding text by traversing flat tree along with testing
+        window.find and execCommand('FindString', false, ~) not walking across shadow boundaries.
+
+        * editing/text-iterator/count-mark-lineboxes-expected.txt:
+        * editing/text-iterator/count-mark-lineboxes.html:
+        * editing/text-iterator/count-mark-simple-lines-expected.txt:
+        * editing/text-iterator/count-mark-simple-lines.html:
+        * editing/text-iterator/count-matches-in-form-expected.txt:
+        * editing/text-iterator/count-matches-in-form.html:
+        * editing/text-iterator/count-matches-in-frames.html:
+        * editing/text-iterator/find-string-on-flat-tree-expected.txt: Added.
+        * editing/text-iterator/find-string-on-flat-tree.html: Added.
+        * fast/text/mark-matches-broken-line-rendering.html:
+        * fast/text/mark-matches-overflow-clip.html:
+        * fast/text/mark-matches-rendering-simple-lines-expected.html:
+        * fast/text/mark-matches-rendering-simple-lines.html:
+        * fast/text/mark-matches-rendering.html:
+
 2017-01-05  Filip Pizlo  <fpi...@apple.com>
 
         Unreviewed, teach run-_javascript_core-tests that this is a slow test.

Modified: trunk/LayoutTests/editing/text-iterator/count-mark-lineboxes-expected.txt (210431 => 210432)


--- trunk/LayoutTests/editing/text-iterator/count-mark-lineboxes-expected.txt	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/LayoutTests/editing/text-iterator/count-mark-lineboxes-expected.txt	2017-01-06 03:45:16 UTC (rev 210432)
@@ -1,8 +1,8 @@
-PASS internals.countMatchesForText('Catilina', 23, 'mark') is 3
+PASS internals.countMatchesForText('Catilina', findOptions, 'mark') is 3
 PASS internals.markerCountForNode(text, 'all') is 3
-PASS internals.countMatchesForText('Roma', 23, 'mark') is 3
+PASS internals.countMatchesForText('Roma', findOptions, 'mark') is 3
 PASS internals.markerCountForNode(text, 'all') is 6
-PASS internals.countMatchesForText('uid', 23, 'mark') is 2
+PASS internals.countMatchesForText('uid', findOptions, 'mark') is 2
 PASS internals.markerCountForNode(text, 'all') is 8
 PASS successfullyParsed is true
 

Modified: trunk/LayoutTests/editing/text-iterator/count-mark-lineboxes.html (210431 => 210432)


--- trunk/LayoutTests/editing/text-iterator/count-mark-lineboxes.html	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/LayoutTests/editing/text-iterator/count-mark-lineboxes.html	2017-01-06 03:45:16 UTC (rev 210432)
@@ -49,11 +49,12 @@
 </p>
 <script>
 var text = document.getElementById('test').firstChild;
-shouldBe("internals.countMatchesForText('Catilina', 23, 'mark')", "3");
+var findOptions = ['CaseInsensitive', 'AtWordStarts', 'TreatMedialCapitalAsWordStart', 'WrapAround'];
+shouldBe("internals.countMatchesForText('Catilina', findOptions, 'mark')", "3");
 shouldBe("internals.markerCountForNode(text, 'all')", "3");
-shouldBe("internals.countMatchesForText('Roma', 23, 'mark')", "3");
+shouldBe("internals.countMatchesForText('Roma', findOptions, 'mark')", "3");
 shouldBe("internals.markerCountForNode(text, 'all')", "6");
-shouldBe("internals.countMatchesForText('uid', 23, 'mark')", "2");
+shouldBe("internals.countMatchesForText('uid', findOptions, 'mark')", "2");
 shouldBe("internals.markerCountForNode(text, 'all')", "8");
 </script>
 <script src=""

Modified: trunk/LayoutTests/editing/text-iterator/count-mark-simple-lines-expected.txt (210431 => 210432)


--- trunk/LayoutTests/editing/text-iterator/count-mark-simple-lines-expected.txt	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/LayoutTests/editing/text-iterator/count-mark-simple-lines-expected.txt	2017-01-06 03:45:16 UTC (rev 210432)
@@ -1,8 +1,8 @@
-PASS internals.countMatchesForText('Catilina', 23, 'mark') is 3
+PASS internals.countMatchesForText('Catilina', findOptions, 'mark') is 3
 PASS internals.markerCountForNode(text, 'all') is 3
-PASS internals.countMatchesForText('Roma', 23, 'mark') is 3
+PASS internals.countMatchesForText('Roma', findOptions, 'mark') is 3
 PASS internals.markerCountForNode(text, 'all') is 6
-PASS internals.countMatchesForText('uid', 23, 'mark') is 2
+PASS internals.countMatchesForText('uid', findOptions, 'mark') is 2
 PASS internals.markerCountForNode(text, 'all') is 8
 PASS successfullyParsed is true
 

Modified: trunk/LayoutTests/editing/text-iterator/count-mark-simple-lines.html (210431 => 210432)


--- trunk/LayoutTests/editing/text-iterator/count-mark-simple-lines.html	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/LayoutTests/editing/text-iterator/count-mark-simple-lines.html	2017-01-06 03:45:16 UTC (rev 210432)
@@ -46,11 +46,12 @@
 </p>
 <script>
 var text = document.getElementById('test').firstChild;
-shouldBe("internals.countMatchesForText('Catilina', 23, 'mark')", "3");
+var findOptions = ['CaseInsensitive', 'AtWordStarts', 'TreatMedialCapitalAsWordStart', 'WrapAround'];
+shouldBe("internals.countMatchesForText('Catilina', findOptions, 'mark')", "3");
 shouldBe("internals.markerCountForNode(text, 'all')", "3");
-shouldBe("internals.countMatchesForText('Roma', 23, 'mark')", "3");
+shouldBe("internals.countMatchesForText('Roma', findOptions, 'mark')", "3");
 shouldBe("internals.markerCountForNode(text, 'all')", "6");
-shouldBe("internals.countMatchesForText('uid', 23, 'mark')", "2");
+shouldBe("internals.countMatchesForText('uid', findOptions, 'mark')", "2");
 shouldBe("internals.markerCountForNode(text, 'all')", "8");
 </script>
 <script src=""

Modified: trunk/LayoutTests/editing/text-iterator/count-matches-in-form-expected.txt (210431 => 210432)


--- trunk/LayoutTests/editing/text-iterator/count-matches-in-form-expected.txt	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/LayoutTests/editing/text-iterator/count-matches-in-form-expected.txt	2017-01-06 03:45:16 UTC (rev 210432)
@@ -1,4 +1,4 @@
-PASS internals.countMatchesForText('rule', 23, '') is 1
+PASS internals.countMatchesForText('rule', ['CaseInsensitive', 'AtWordStarts', 'TreatMedialCapitalAsWordStart', 'WrapAround'], '') is 1
 PASS successfullyParsed is true
 
 TEST COMPLETE

Modified: trunk/LayoutTests/editing/text-iterator/count-matches-in-form.html (210431 => 210432)


--- trunk/LayoutTests/editing/text-iterator/count-matches-in-form.html	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/LayoutTests/editing/text-iterator/count-matches-in-form.html	2017-01-06 03:45:16 UTC (rev 210432)
@@ -3,6 +3,6 @@
 <body>
 <fieldset><input value="rule"></fieldset>
 <script>
-shouldBe("internals.countMatchesForText('rule', 23, '')", "1");
+shouldBe("internals.countMatchesForText('rule', ['CaseInsensitive', 'AtWordStarts', 'TreatMedialCapitalAsWordStart', 'WrapAround'], '')", "1");
 </script>
 <script src=""

Modified: trunk/LayoutTests/editing/text-iterator/count-matches-in-frames.html (210431 => 210432)


--- trunk/LayoutTests/editing/text-iterator/count-matches-in-frames.html	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/LayoutTests/editing/text-iterator/count-matches-in-frames.html	2017-01-06 03:45:16 UTC (rev 210432)
@@ -25,7 +25,7 @@
 
     frame.contentDocument.body.innerHTML = findString;
 
-    assert_equals(internals.countFindMatches(findString, 0, ''), shouldFindInFrame ? 2 : 1);
+    assert_equals(internals.countFindMatches(findString, [], ''), shouldFindInFrame ? 2 : 1);
 }
 
 test(function () {

Added: trunk/LayoutTests/editing/text-iterator/find-string-on-flat-tree-expected.txt (0 => 210432)


--- trunk/LayoutTests/editing/text-iterator/find-string-on-flat-tree-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/text-iterator/find-string-on-flat-tree-expected.txt	2017-01-06 03:45:16 UTC (rev 210432)
@@ -0,0 +1,83 @@
+This tests finding across shadow boundaries using the flat tree.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS clearSelection(); document.execCommand('FindString', null, 'in-document'); selectedText() is '(#test-content, 0) 0 to 11'
+PASS clearSelection(); window.find('in-document'); selectedText() is '(#test-content, 0) 0 to 11'
+PASS rangeText(internals.rangeOfString('in-document', null, ['DoNotTraverseFlatTree'])) is '(#test-content, 0) 0 to 11'
+PASS rangeText(internals.rangeOfString('in-document', null, [])) is '(#test-content, 0) 0 to 11'
+PASS clearSelection(); document.execCommand('FindString', null, 'in-shadow'); selectedText() is null
+PASS clearSelection(); window.find('in-shadow'); selectedText() is null
+PASS rangeText(internals.rangeOfString('in-shadow', null, ['DoNotTraverseFlatTree'])) is null
+PASS rangeText(internals.rangeOfString('in-shadow', null, [])) is '(#shadow-root, 0) 0 to 9'
+PASS clearSelection(); document.execCommand('FindString', null, 'unslotted'); selectedText() is null
+PASS clearSelection(); window.find('unslotted'); selectedText() is null
+PASS rangeText(internals.rangeOfString('unslotted', null, ['DoNotTraverseFlatTree'])) is null
+PASS rangeText(internals.rangeOfString('unslotted', null, [])) is null
+PASS clearSelection(); document.execCommand('FindString', null, 'slotted'); selectedText() is '(#slotted-element, 0) 0 to 7'
+PASS clearSelection(); window.find('slotted'); selectedText() is '(#slotted-element, 0) 0 to 7'
+PASS rangeText(internals.rangeOfString('slotted', null, ['DoNotTraverseFlatTree'])) is '(#slotted-element, 0) 0 to 7'
+PASS rangeText(internals.rangeOfString('slotted', null, [])) is '(#slotted-element, 0) 0 to 7'
+PASS clearSelection(); document.execCommand('FindString', null, 'slotted in-document'); selectedText() is '((#slotted-element, 0), 0) to ((#test-content, 2), 12)'
+PASS clearSelection(); window.find('slotted in-document'); selectedText() is '((#slotted-element, 0), 0) to ((#test-content, 2), 12)'
+PASS rangeText(internals.rangeOfString('slotted in-document', null, ['DoNotTraverseFlatTree'])) is '((#slotted-element, 0), 0) to ((#test-content, 2), 12)'
+PASS rangeText(internals.rangeOfString('slotted in-document', null, [])) is null
+PASS clearSelection(); document.execCommand('FindString', null, 'in-shadow in-document'); selectedText() is null
+PASS clearSelection(); window.find('in-shadow in-document'); selectedText() is null
+PASS rangeText(internals.rangeOfString('in-shadow in-document', null, ['DoNotTraverseFlatTree'])) is null
+PASS rangeText(internals.rangeOfString('in-shadow in-document', null, [])) is null /* Can't return a range across shadow boundary */
+PASS setSelection(testContent, 1); document.execCommand('FindString', null, 'in-document'); selectedText() is '(#test-content, 2) 1 to 12'
+PASS setSelection(testContent, 1); window.find('in-document'); selectedText() is '(#test-content, 2) 1 to 12'
+PASS rangeText(internals.rangeOfString('in-document', range(testContent, 1), ['DoNotTraverseFlatTree'])) is '(#test-content, 2) 1 to 12'
+PASS rangeText(internals.rangeOfString('in-document', range(testContent, 1), [])) is '(#test-content, 2) 1 to 12'
+PASS setSelection(shadowRoot, 0); document.execCommand('FindString', null, 'in-document'); selectedText() is '(#test-content, 2) 1 to 12'
+PASS setSelection(shadowRoot, 0); window.find('in-document'); selectedText() is '(#test-content, 2) 1 to 12'
+PASS rangeText(internals.rangeOfString('in-document', range(shadowRoot, 0), ['DoNotTraverseFlatTree'])) is '(#test-content, 2) 1 to 12'
+PASS rangeText(internals.rangeOfString('in-document', range(shadowRoot, 0), [])) is '(#test-content, 2) 1 to 12'
+PASS setSelection(shadowRoot, 0); document.execCommand('FindString', null, 'in-shadow'); selectedText() is '#test-content 1 to 1'
+PASS setSelection(shadowRoot, 0); window.find('in-shadow'); selectedText() is '#test-content 1 to 1'
+PASS rangeText(internals.rangeOfString('in-shadow', range(shadowRoot, 0), ['DoNotTraverseFlatTree'])) is '(#shadow-root, 0) 0 to 9'
+PASS rangeText(internals.rangeOfString('in-shadow', range(shadowRoot, 0), [])) is '(#shadow-root, 0) 0 to 9'
+PASS setSelection(shadowRoot, 0); document.execCommand('FindString', null, 'slotted'); selectedText() is '(#slotted-element, 0) 0 to 7' /* Wrapped around */
+PASS setSelection(shadowRoot, 0); window.find('slotted'); selectedText() is '#test-content 1 to 1'
+PASS setSelection(shadowRoot, 0); window.find('slotted', /* caseSensitive */ true, /* backwards */ false, /* wrap */ true); selectedText() is '(#slotted-element, 0) 0 to 7'
+PASS rangeText(internals.rangeOfString('slotted', range(shadowRoot, 0), ['DoNotTraverseFlatTree'])) is null
+PASS rangeText(internals.rangeOfString('slotted', range(shadowRoot, 0), [])) is '(#slotted-element, 0) 0 to 7'
+PASS setSelection(shadowRoot, 1); document.execCommand('FindString', null, 'slotted'); selectedText() is '(#slotted-element, 0) 0 to 7' /* Wrapped around */
+PASS setSelection(shadowRoot, 1); window.find('slotted'); selectedText() is '#test-content 1 to 1'
+PASS setSelection(shadowRoot, 1); window.find('slotted', /* caseSensitive */ true, /* backwards */ false, /* wrap */ true); selectedText() is '(#slotted-element, 0) 0 to 7'
+PASS rangeText(internals.rangeOfString('slotted', range(shadowRoot, 1), ['DoNotTraverseFlatTree'])) is null
+PASS rangeText(internals.rangeOfString('slotted', range(shadowRoot, 1), [])) is '(#slotted-element, 0) 0 to 7'
+PASS setSelection(shadowRoot, 1); document.execCommand('FindString', null, 'in-shadow'); selectedText() is '#test-content 1 to 1'
+PASS setSelection(shadowRoot, 1); window.find('in-shadow'); selectedText() is '#test-content 1 to 1'
+PASS rangeText(internals.rangeOfString('in-shadow', range(shadowRoot, 1), ['DoNotTraverseFlatTree'])) is '(#shadow-root, 2) 1 to 10'
+PASS rangeText(internals.rangeOfString('in-shadow', range(shadowRoot, 1), [])) is '(#shadow-root, 2) 1 to 10'
+PASS setSelection(shadowRoot, 1); document.execCommand('FindString', null, 'in-document'); selectedText() is '(#test-content, 2) 1 to 12'
+PASS setSelection(shadowRoot, 1); window.find('in-document'); selectedText() is '(#test-content, 2) 1 to 12'
+PASS rangeText(internals.rangeOfString('in-document', range(shadowRoot, 1), ['DoNotTraverseFlatTree'])) is '(#test-content, 2) 1 to 12'
+PASS rangeText(internals.rangeOfString('in-document', range(shadowRoot, 1), [])) is '(#test-content, 2) 1 to 12'
+PASS setSelection(shadowRoot, 1); document.execCommand('FindString', null, 'in-slot'); selectedText() is '#test-content 1 to 1'
+PASS setSelection(shadowRoot, 1); window.find('in-slot'); selectedText() is '#test-content 1 to 1'
+PASS rangeText(internals.rangeOfString('in-slot', range(shadowRoot, 1), ['DoNotTraverseFlatTree'])) is null
+PASS rangeText(internals.rangeOfString('in-slot', range(shadowRoot, 1), [])) is null
+PASS clearSelection(); document.execCommand('FindString', null, 'in-user-agent-shadow'); selectedText() is null
+PASS clearSelection(); window.find('in-user-agent-shadow'); selectedText() is null
+PASS rangeText(internals.rangeOfString('in-user-agent-shadow', null, ['DoNotTraverseFlatTree'])) is null
+PASS rangeText(internals.rangeOfString('in-user-agent-shadow', null, [])) is null
+PASS setSelection(userAgentShadowRoot, 0); document.execCommand('FindString', null, 'in-user-agent-shadow'); selectedText() is '#test-content 3 to 3'
+PASS setSelection(userAgentShadowRoot, 0); window.find('in-user-agent-shadow'); selectedText() is '#test-content 3 to 3'
+PASS rangeText(internals.rangeOfString('in-user-agent-shadow', range(userAgentShadowRoot, 0), ['DoNotTraverseFlatTree'])) is '(#user-agent-shadow-root, 0) 0 to 20'
+PASS rangeText(internals.rangeOfString('in-user-agent-shadow', range(userAgentShadowRoot, 0), [])) is '(#user-agent-shadow-root, 0) 0 to 20'
+PASS clearSelection(); internals.countFindMatches('in-document', ['DoNotTraverseFlatTree']) is 2
+PASS internals.countFindMatches('in-document', []) is 2
+PASS internals.countFindMatches('in-shadow', ['DoNotTraverseFlatTree']) is 0
+PASS internals.countFindMatches('in-shadow', []) is 2
+PASS internals.countFindMatches('in-', ['DoNotTraverseFlatTree']) is 2
+PASS internals.countFindMatches('in-', []) is 4
+PASS internals.countFindMatches('in-shadow in-document', ['DoNotTraverseFlatTree']) is 0
+PASS internals.countFindMatches('in-shadow in-document', []) is 0
+PASS successfullyParsed is true
+
+TEST COMPLETE
+in-document slotted in-document

Added: trunk/LayoutTests/editing/text-iterator/find-string-on-flat-tree.html (0 => 210432)


--- trunk/LayoutTests/editing/text-iterator/find-string-on-flat-tree.html	                        (rev 0)
+++ trunk/LayoutTests/editing/text-iterator/find-string-on-flat-tree.html	2017-01-06 03:45:16 UTC (rev 210432)
@@ -0,0 +1,169 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div id="test-content">in-document <span id="host">unslotted <b id="slotted-element" slot="named-slot">slotted</b> unslotted</span> in-document<span id="user-agent-host"></span></div>
+<script src=""
+<script>
+
+description('This tests finding across shadow boundaries using the flat tree.');
+
+var testContent = document.getElementById('test-content');
+var shadowHost = document.getElementById('host');
+var shadowRoot = shadowHost.attachShadow({mode: 'closed'});
+shadowRoot.innerHTML = 'in-shadow <slot name="named-slot">in-slot</slot> in-shadow';
+shadowRoot.id = 'shadow-root';
+
+if (!window.internals)
+    testFailed('This test requires internals');
+else {
+    var userAgentShadowHost = document.getElementById('user-agent-host');
+    var userAgentShadowRoot = internals.ensureUserAgentShadowRoot(userAgentShadowHost);
+    userAgentShadowRoot.innerHTML = 'in-user-agent-shadow';
+    userAgentShadowRoot.id = 'user-agent-shadow-root';
+
+    // Hide console so that we don't end up finding text in the log itself.
+    document.getElementById('console').style.display = 'none';
+
+    function nodeLabel(node)
+    {
+        if (node.nodeType != Node.TEXT_NODE)
+            return '#' + node.id;
+        let offset = 0;
+        for (let child = node.previousSibling; child; child = child.previousSibling)
+            offset++;
+        return `(#${node.parentNode.id}, ${offset})`;
+    }
+
+    function rangeText(range)
+    {
+        if (!range)
+            return null;
+        if (range.startContainer == range.endContainer)
+            return `${nodeLabel(range.startContainer)} ${range.startOffset} to ${range.endOffset}`;
+        return `(${nodeLabel(range.startContainer)}, ${range.startOffset}) to (${nodeLabel(range.endContainer)}, ${range.endOffset})`;
+    }
+
+    function clearSelection()
+    {
+        getSelection().removeAllRanges();
+    }
+
+    function selectedText(range)
+    {
+        if (!getSelection().rangeCount)
+            return null;
+        return rangeText(getSelection().getRangeAt(0));
+    }
+
+    function setSelection(node, offset)
+    {
+        getSelection().setPosition(node, offset);
+    }
+
+    function range(node, offset)
+    {
+        let range = new Range;
+        range.setStart(node, offset);
+        return range;
+    }
+
+    shouldBe("clearSelection(); document.execCommand('FindString', null, 'in-document'); selectedText()", "'(#test-content, 0) 0 to 11'");
+    shouldBe("clearSelection(); window.find('in-document'); selectedText()", "'(#test-content, 0) 0 to 11'");
+    shouldBe("rangeText(internals.rangeOfString('in-document', null, ['DoNotTraverseFlatTree']))", "'(#test-content, 0) 0 to 11'");
+    shouldBe("rangeText(internals.rangeOfString('in-document', null, []))", "'(#test-content, 0) 0 to 11'");
+
+    shouldBe("clearSelection(); document.execCommand('FindString', null, 'in-shadow'); selectedText()", "null");
+    shouldBe("clearSelection(); window.find('in-shadow'); selectedText()", "null");
+    shouldBe("rangeText(internals.rangeOfString('in-shadow', null, ['DoNotTraverseFlatTree']))", "null");
+    shouldBe("rangeText(internals.rangeOfString('in-shadow', null, []))", "'(#shadow-root, 0) 0 to 9'");
+
+    shouldBe("clearSelection(); document.execCommand('FindString', null, 'unslotted'); selectedText()", "null");
+    shouldBe("clearSelection(); window.find('unslotted'); selectedText()", "null");
+    shouldBe("rangeText(internals.rangeOfString('unslotted', null, ['DoNotTraverseFlatTree']))", "null");
+    shouldBe("rangeText(internals.rangeOfString('unslotted', null, []))", "null");
+
+    shouldBe("clearSelection(); document.execCommand('FindString', null, 'slotted'); selectedText()", "'(#slotted-element, 0) 0 to 7'");
+    shouldBe("clearSelection(); window.find('slotted'); selectedText()", "'(#slotted-element, 0) 0 to 7'");
+    shouldBe("rangeText(internals.rangeOfString('slotted', null, ['DoNotTraverseFlatTree']))", "'(#slotted-element, 0) 0 to 7'");
+    shouldBe("rangeText(internals.rangeOfString('slotted', null, []))", "'(#slotted-element, 0) 0 to 7'");
+
+    shouldBe("clearSelection(); document.execCommand('FindString', null, 'slotted in-document'); selectedText()", "'((#slotted-element, 0), 0) to ((#test-content, 2), 12)'");
+    shouldBe("clearSelection(); window.find('slotted in-document'); selectedText()", "'((#slotted-element, 0), 0) to ((#test-content, 2), 12)'");
+    shouldBe("rangeText(internals.rangeOfString('slotted in-document', null, ['DoNotTraverseFlatTree']))", "'((#slotted-element, 0), 0) to ((#test-content, 2), 12)'");
+    shouldBe("rangeText(internals.rangeOfString('slotted in-document', null, []))", "null");
+
+    shouldBe("clearSelection(); document.execCommand('FindString', null, 'in-shadow in-document'); selectedText()", "null");
+    shouldBe("clearSelection(); window.find('in-shadow in-document'); selectedText()", "null");
+    shouldBe("rangeText(internals.rangeOfString('in-shadow in-document', null, ['DoNotTraverseFlatTree']))", "null");
+    shouldBe("rangeText(internals.rangeOfString('in-shadow in-document', null, []))", "null /* Can't return a range across shadow boundary */");
+
+    shouldBe("setSelection(testContent, 1); document.execCommand('FindString', null, 'in-document'); selectedText()", "'(#test-content, 2) 1 to 12'");
+    shouldBe("setSelection(testContent, 1); window.find('in-document'); selectedText()", "'(#test-content, 2) 1 to 12'");
+    shouldBe("rangeText(internals.rangeOfString('in-document', range(testContent, 1), ['DoNotTraverseFlatTree']))", "'(#test-content, 2) 1 to 12'");
+    shouldBe("rangeText(internals.rangeOfString('in-document', range(testContent, 1), []))", "'(#test-content, 2) 1 to 12'");
+
+    shouldBe("setSelection(shadowRoot, 0); document.execCommand('FindString', null, 'in-document'); selectedText()", "'(#test-content, 2) 1 to 12'");
+    shouldBe("setSelection(shadowRoot, 0); window.find('in-document'); selectedText()", "'(#test-content, 2) 1 to 12'");
+    shouldBe("rangeText(internals.rangeOfString('in-document', range(shadowRoot, 0), ['DoNotTraverseFlatTree']))", "'(#test-content, 2) 1 to 12'");
+    shouldBe("rangeText(internals.rangeOfString('in-document', range(shadowRoot, 0), []))", "'(#test-content, 2) 1 to 12'");
+
+    shouldBe("setSelection(shadowRoot, 0); document.execCommand('FindString', null, 'in-shadow'); selectedText()", "'#test-content 1 to 1'");
+    shouldBe("setSelection(shadowRoot, 0); window.find('in-shadow'); selectedText()", "'#test-content 1 to 1'");
+    shouldBe("rangeText(internals.rangeOfString('in-shadow', range(shadowRoot, 0), ['DoNotTraverseFlatTree']))", "'(#shadow-root, 0) 0 to 9'");
+    shouldBe("rangeText(internals.rangeOfString('in-shadow', range(shadowRoot, 0), []))", "'(#shadow-root, 0) 0 to 9'");
+
+    shouldBe("setSelection(shadowRoot, 0); document.execCommand('FindString', null, 'slotted'); selectedText()", "'(#slotted-element, 0) 0 to 7' /* Wrapped around */");
+    shouldBe("setSelection(shadowRoot, 0); window.find('slotted'); selectedText()", "'#test-content 1 to 1'");
+    shouldBe("setSelection(shadowRoot, 0); window.find('slotted', /* caseSensitive */ true, /* backwards */ false, /* wrap */ true); selectedText()", "'(#slotted-element, 0) 0 to 7'");
+    shouldBe("rangeText(internals.rangeOfString('slotted', range(shadowRoot, 0), ['DoNotTraverseFlatTree']))", "null");
+    shouldBe("rangeText(internals.rangeOfString('slotted', range(shadowRoot, 0), []))", "'(#slotted-element, 0) 0 to 7'");
+
+    shouldBe("setSelection(shadowRoot, 1); document.execCommand('FindString', null, 'slotted'); selectedText()", "'(#slotted-element, 0) 0 to 7' /* Wrapped around */");
+    shouldBe("setSelection(shadowRoot, 1); window.find('slotted'); selectedText()", "'#test-content 1 to 1'");
+    shouldBe("setSelection(shadowRoot, 1); window.find('slotted', /* caseSensitive */ true, /* backwards */ false, /* wrap */ true); selectedText()", "'(#slotted-element, 0) 0 to 7'");
+    shouldBe("rangeText(internals.rangeOfString('slotted', range(shadowRoot, 1), ['DoNotTraverseFlatTree']))", "null");
+    shouldBe("rangeText(internals.rangeOfString('slotted', range(shadowRoot, 1), []))", "'(#slotted-element, 0) 0 to 7'");
+
+    shouldBe("setSelection(shadowRoot, 1); document.execCommand('FindString', null, 'in-shadow'); selectedText()", "'#test-content 1 to 1'");
+    shouldBe("setSelection(shadowRoot, 1); window.find('in-shadow'); selectedText()", "'#test-content 1 to 1'");
+    shouldBe("rangeText(internals.rangeOfString('in-shadow', range(shadowRoot, 1), ['DoNotTraverseFlatTree']))", "'(#shadow-root, 2) 1 to 10'");
+    shouldBe("rangeText(internals.rangeOfString('in-shadow', range(shadowRoot, 1), []))", "'(#shadow-root, 2) 1 to 10'");
+
+    shouldBe("setSelection(shadowRoot, 1); document.execCommand('FindString', null, 'in-document'); selectedText()", "'(#test-content, 2) 1 to 12'");
+    shouldBe("setSelection(shadowRoot, 1); window.find('in-document'); selectedText()", "'(#test-content, 2) 1 to 12'");
+    shouldBe("rangeText(internals.rangeOfString('in-document', range(shadowRoot, 1), ['DoNotTraverseFlatTree']))", "'(#test-content, 2) 1 to 12'");
+    shouldBe("rangeText(internals.rangeOfString('in-document', range(shadowRoot, 1), []))", "'(#test-content, 2) 1 to 12'");
+
+    shouldBe("setSelection(shadowRoot, 1); document.execCommand('FindString', null, 'in-slot'); selectedText()", "'#test-content 1 to 1'");
+    shouldBe("setSelection(shadowRoot, 1); window.find('in-slot'); selectedText()", "'#test-content 1 to 1'");
+    shouldBe("rangeText(internals.rangeOfString('in-slot', range(shadowRoot, 1), ['DoNotTraverseFlatTree']))", "null");
+    shouldBe("rangeText(internals.rangeOfString('in-slot', range(shadowRoot, 1), []))", "null");
+
+    shouldBe("clearSelection(); document.execCommand('FindString', null, 'in-user-agent-shadow'); selectedText()", "null");
+    shouldBe("clearSelection(); window.find('in-user-agent-shadow'); selectedText()", "null");
+    shouldBe("rangeText(internals.rangeOfString('in-user-agent-shadow', null, ['DoNotTraverseFlatTree']))", "null");
+    shouldBe("rangeText(internals.rangeOfString('in-user-agent-shadow', null, []))", "null");
+
+    shouldBe("setSelection(userAgentShadowRoot, 0); document.execCommand('FindString', null, 'in-user-agent-shadow'); selectedText()", "'#test-content 3 to 3'");
+    shouldBe("setSelection(userAgentShadowRoot, 0); window.find('in-user-agent-shadow'); selectedText()", "'#test-content 3 to 3'");
+    shouldBe("rangeText(internals.rangeOfString('in-user-agent-shadow', range(userAgentShadowRoot, 0), ['DoNotTraverseFlatTree']))", "'(#user-agent-shadow-root, 0) 0 to 20'");
+    shouldBe("rangeText(internals.rangeOfString('in-user-agent-shadow', range(userAgentShadowRoot, 0), []))", "'(#user-agent-shadow-root, 0) 0 to 20'");
+
+    shouldBe("clearSelection(); internals.countFindMatches('in-document', ['DoNotTraverseFlatTree'])", "2");
+    shouldBe("internals.countFindMatches('in-document', [])", "2");
+
+    shouldBe("internals.countFindMatches('in-shadow', ['DoNotTraverseFlatTree'])", "0");
+    shouldBe("internals.countFindMatches('in-shadow', [])", "2");
+
+    shouldBe("internals.countFindMatches('in-', ['DoNotTraverseFlatTree'])", "2");
+    shouldBe("internals.countFindMatches('in-', [])", "4");
+
+    shouldBe("internals.countFindMatches('in-shadow in-document', ['DoNotTraverseFlatTree'])", "0");
+    shouldBe("internals.countFindMatches('in-shadow in-document', [])", "0");
+
+    document.getElementById('console').style.display = null;
+}
+
+</script>
+</body>
+</html>

Modified: trunk/LayoutTests/fast/text/mark-matches-broken-line-rendering.html (210431 => 210432)


--- trunk/LayoutTests/fast/text/mark-matches-broken-line-rendering.html	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/LayoutTests/fast/text/mark-matches-broken-line-rendering.html	2017-01-06 03:45:16 UTC (rev 210432)
@@ -11,6 +11,6 @@
 <script>
 if (window.internals) {
     internals.setMarkedTextMatchesAreHighlighted(true);
-    internals.countMatchesForText("Quo usque tandem abutere, Catilina, patientia nostra?", 0, "mark");
+    internals.countMatchesForText("Quo usque tandem abutere, Catilina, patientia nostra?", [], "mark");
 }
 </script>

Modified: trunk/LayoutTests/fast/text/mark-matches-overflow-clip.html (210431 => 210432)


--- trunk/LayoutTests/fast/text/mark-matches-overflow-clip.html	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/LayoutTests/fast/text/mark-matches-overflow-clip.html	2017-01-06 03:45:16 UTC (rev 210432)
@@ -17,7 +17,7 @@
     }
 
     testRunner.dumpAsText();
-    internals.countMatchesForText("tandem", 0, "mark");
+    internals.countMatchesForText("tandem", [], "mark");
     
     var markedNode = document.getElementsByTagName("span")[0].firstChild;
     document.write(internals.dumpMarkerRects("TextMatch"));

Modified: trunk/LayoutTests/fast/text/mark-matches-rendering-simple-lines-expected.html (210431 => 210432)


--- trunk/LayoutTests/fast/text/mark-matches-rendering-simple-lines-expected.html	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/LayoutTests/fast/text/mark-matches-rendering-simple-lines-expected.html	2017-01-06 03:45:16 UTC (rev 210432)
@@ -8,6 +8,6 @@
 <script>
 if (window.internals) {
     internals.setMarkedTextMatchesAreHighlighted(true);
-    internals.countMatchesForText("ti", 0, "mark");
+    internals.countMatchesForText("ti", [], "mark");
 }
 </script>

Modified: trunk/LayoutTests/fast/text/mark-matches-rendering-simple-lines.html (210431 => 210432)


--- trunk/LayoutTests/fast/text/mark-matches-rendering-simple-lines.html	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/LayoutTests/fast/text/mark-matches-rendering-simple-lines.html	2017-01-06 03:45:16 UTC (rev 210432)
@@ -4,6 +4,6 @@
 <script>
 if (window.internals) {
     internals.setMarkedTextMatchesAreHighlighted(true);
-    internals.countMatchesForText("ti", 0, "mark");
+    internals.countMatchesForText("ti", [], "mark");
 }
 </script>

Modified: trunk/LayoutTests/fast/text/mark-matches-rendering.html (210431 => 210432)


--- trunk/LayoutTests/fast/text/mark-matches-rendering.html	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/LayoutTests/fast/text/mark-matches-rendering.html	2017-01-06 03:45:16 UTC (rev 210432)
@@ -4,6 +4,6 @@
 <script>
 if (window.internals) {
     internals.setMarkedTextMatchesAreHighlighted(true);
-    internals.countMatchesForText("Quo usque tandem abutere, Catilina, patientia nostra?", 0, "mark");
+    internals.countMatchesForText("Quo usque tandem abutere, Catilina, patientia nostra?", [], "mark");
 }
 </script>

Modified: trunk/Source/WebCore/ChangeLog (210431 => 210432)


--- trunk/Source/WebCore/ChangeLog	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/Source/WebCore/ChangeLog	2017-01-06 03:45:16 UTC (rev 210432)
@@ -1,3 +1,54 @@
+2017-01-05  Ryosuke Niwa  <rn...@webkit.org>
+
+        Finding text doesn't work across shadow boundary
+        https://bugs.webkit.org/show_bug.cgi?id=158503
+
+        Reviewed by Antti Koivisto.
+
+        Added a new TextIterator behavior flag, TextIteratorTraversesFlatTree, which makes TextIterator traverse
+        the flat tree instead of the DOM tree, and made this behavior default in findPlainText.
+
+        Also added a new find options flag, DoNotTraverseFlatTree, to suppress this behavior in window.find(~)
+        and execCommand('FindString', false, ~) as they should not be able to peek information inside shadow trees.
+        Unfortunately these APIs have been deprecated in the standards so there is no specification to follow.
+
+        For now, we don't support finding a word or a keyword across a shadow boundary as this would require
+        making rangeOfString and other related functions return a Range-like object that can cross shadow boundaries.
+
+        Also added internals.rangeOfString to test Editor::rangeOfString, and replaced the bit-flag arguments
+        to internals.countMatchesForText and internals.countFindMatches by an array of strings for better portability.
+
+        Test: editing/text-iterator/find-string-on-flat-tree.html
+
+        * editing/Editor.cpp:
+        (WebCore::Editor::rangeOfString): Use the modern containingShadowRoot instead of nonBoundaryShadowTreeRootNode
+        since the start container can be a shadow root, which nonBoundaryShadowTreeRootNode asserts not be the case.
+        * editing/Editor.h:
+        * editing/EditorCommand.cpp:
+        (WebCore::executeFindString): Don't traverse across shadow boundaries.
+        * editing/FindOptions.h: Added DoNotTraverseFlatTree.
+        * editing/TextIterator.cpp:
+        (WebCore::assignedAuthorSlot): Added.
+        (WebCore::authorShadowRoot): Added.
+        (WebCore::firstChildInFlatTreeIgnoringUserAgentShadow): Added.
+        (WebCore::nextSiblingInFlatTreeIgnoringUserAgentShadow): Added.
+        (WebCore::firstChild): Added. Traverses the flat tree when TextIteratorTraversesFlatTree is set.
+        (WebCore::nextSibling): Ditto.
+        (WebCore::parentNodeOrShadowHost): Ditto.
+        (WebCore::TextIterator::advance): Don't set m_handledChildren to true when the current node has display: contents.
+        (WebCore::findPlainText): Use TextIteratorTraversesFlatTree unless DoNotTraverseFlatTree is set.
+        * editing/TextIteratorBehavior.h: Added TextIteratorTraversesFlatTree.
+        * page/DOMWindow.cpp:
+        (WebCore::DOMWindow::find): Don't traverse across shadow boundaries.
+        * testing/Internals.cpp:
+        (WebCore::parseFindOptions): Added.
+        (WebCore::Internals::rangeOfString): Added.
+        (WebCore::Internals::countMatchesForText): Replaced the find options by an array of strings instead of a bit mask.
+        (WebCore::Internals::countFindMatches): Ditto.
+        * testing/Internals.h:
+        * testing/Internals.idl: Added rangeOfString, and replaced find options bit-flag in countMatchesForText and
+        countFindMatches by an array of strings so that the tests themselves don't rely on a specific value of each bit flag.
+
 2017-01-05  Chris Dumez  <cdu...@apple.com>
 
         [Form Validation] lengthy validation messages should be truncated with an ellipsis

Modified: trunk/Source/WebCore/editing/Editor.cpp (210431 => 210432)


--- trunk/Source/WebCore/editing/Editor.cpp	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/Source/WebCore/editing/Editor.cpp	2017-01-06 03:45:16 UTC (rev 210432)
@@ -3132,7 +3132,7 @@
             searchRange->setEnd(startInReferenceRange ? referenceRange->endPosition() : referenceRange->startPosition());
     }
 
-    RefPtr<Node> shadowTreeRoot = referenceRange ? referenceRange->startContainer().nonBoundaryShadowTreeRootNode() : nullptr;
+    RefPtr<ShadowRoot> shadowTreeRoot = referenceRange ? referenceRange->startContainer().containingShadowRoot() : nullptr;
     if (shadowTreeRoot) {
         if (forward)
             searchRange->setEnd(*shadowTreeRoot, shadowTreeRoot->countChildNodes());

Modified: trunk/Source/WebCore/editing/Editor.h (210431 => 210432)


--- trunk/Source/WebCore/editing/Editor.h	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/Source/WebCore/editing/Editor.h	2017-01-06 03:45:16 UTC (rev 210432)
@@ -378,7 +378,7 @@
     String selectedTextForDataTransfer() const;
     WEBCORE_EXPORT bool findString(const String&, FindOptions);
 
-    RefPtr<Range> rangeOfString(const String&, Range*, FindOptions);
+    WEBCORE_EXPORT RefPtr<Range> rangeOfString(const String&, Range*, FindOptions);
 
     const VisibleSelection& mark() const; // Mark, to be used as emacs uses it.
     void setMark(const VisibleSelection&);

Modified: trunk/Source/WebCore/editing/EditorCommand.cpp (210431 => 210432)


--- trunk/Source/WebCore/editing/EditorCommand.cpp	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/Source/WebCore/editing/EditorCommand.cpp	2017-01-06 03:45:16 UTC (rev 210432)
@@ -377,7 +377,7 @@
 
 static bool executeFindString(Frame& frame, Event*, EditorCommandSource, const String& value)
 {
-    return frame.editor().findString(value, CaseInsensitive | WrapAround);
+    return frame.editor().findString(value, CaseInsensitive | WrapAround | DoNotTraverseFlatTree);
 }
 
 static bool executeFontName(Frame& frame, Event*, EditorCommandSource source, const String& value)

Modified: trunk/Source/WebCore/editing/FindOptions.h (210431 => 210432)


--- trunk/Source/WebCore/editing/FindOptions.h	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/Source/WebCore/editing/FindOptions.h	2017-01-06 03:45:16 UTC (rev 210432)
@@ -37,9 +37,10 @@
     WrapAround = 1 << 4,
     StartInSelection = 1 << 5,
     DoNotRevealSelection = 1 << 6,
-    AtWordEnds = 1 << 7
+    AtWordEnds = 1 << 7,
+    DoNotTraverseFlatTree = 1 << 8,
 };
 
-typedef unsigned char FindOptions;
+typedef unsigned short FindOptions;
 
 } // namespace WebCore

Modified: trunk/Source/WebCore/editing/TextIterator.cpp (210431 => 210432)


--- trunk/Source/WebCore/editing/TextIterator.cpp	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/Source/WebCore/editing/TextIterator.cpp	2017-01-06 03:45:16 UTC (rev 210432)
@@ -381,6 +381,69 @@
 {
 }
 
+static HTMLSlotElement* assignedAuthorSlot(Node& node)
+{
+    auto* slot = node.assignedSlot();
+    if (!slot || slot->containingShadowRoot()->mode() == ShadowRootMode::UserAgent)
+        return nullptr;
+    return slot;
+}
+
+static ShadowRoot* authorShadowRoot(Node& node)
+{
+    auto* shadowRoot = node.shadowRoot();
+    if (!shadowRoot || shadowRoot->mode() == ShadowRootMode::UserAgent)
+        return nullptr;
+    return shadowRoot;
+}
+
+// FIXME: Use ComposedTreeIterator instead. These functions are more expensive because they might do O(n) work.
+static inline Node* firstChildInFlatTreeIgnoringUserAgentShadow(Node& node)
+{
+    if (auto* shadowRoot = authorShadowRoot(node))
+        return shadowRoot->firstChild();
+    if (is<HTMLSlotElement>(node)) {
+        if (auto* assignedNodes = downcast<HTMLSlotElement>(node).assignedNodes())
+            return assignedNodes->at(0);
+    }
+    return node.firstChild();
+}
+
+static inline Node* nextSiblingInFlatTreeIgnoringUserAgentShadow(Node& node)
+{
+    if (auto* slot = assignedAuthorSlot(node)) {
+        auto* assignedNodes = slot->assignedNodes();
+        ASSERT(assignedNodes);
+        auto nodeIndex = assignedNodes->find(&node);
+        ASSERT(nodeIndex != notFound);
+        if (assignedNodes->size() > nodeIndex + 1)
+            return assignedNodes->at(nodeIndex + 1);
+        return nullptr;
+    }
+    return node.nextSibling();
+}
+
+static inline Node* firstChild(TextIteratorBehavior options, Node& node)
+{
+    if (UNLIKELY(options & TextIteratorTraversesFlatTree))
+        return firstChildInFlatTreeIgnoringUserAgentShadow(node);
+    return node.firstChild();
+}
+
+static inline Node* nextSibling(TextIteratorBehavior options, Node& node)
+{
+    if (UNLIKELY(options & TextIteratorTraversesFlatTree))
+        return nextSiblingInFlatTreeIgnoringUserAgentShadow(node);
+    return node.nextSibling();
+}
+
+static inline Node* parentNodeOrShadowHost(TextIteratorBehavior options, Node& node)
+{
+    if (UNLIKELY(options & TextIteratorTraversesFlatTree))
+        return node.parentInComposedTree();
+    return node.parentOrShadowHostNode();
+}
+
 void TextIterator::advance()
 {
     ASSERT(!atEnd());
@@ -430,7 +493,7 @@
         auto* renderer = m_node->renderer();
         if (!renderer) {
             m_handledNode = true;
-            m_handledChildren = true;
+            m_handledChildren = !((m_behavior & TextIteratorTraversesFlatTree) && is<Element>(*m_node) && downcast<Element>(*m_node).hasDisplayContents());
         } else {
             // handle current node according to its type
             if (!m_handledNode) {
@@ -447,13 +510,13 @@
 
         // find a new current node to handle in depth-first manner,
         // calling exitNode() as we come back thru a parent node
-        Node* next = m_handledChildren ? nullptr : m_node->firstChild();
+        Node* next = m_handledChildren ? nullptr : firstChild(m_behavior, *m_node);
         m_offset = 0;
         if (!next) {
-            next = m_node->nextSibling();
+            next = nextSibling(m_behavior, *m_node);
             if (!next) {
                 bool pastEnd = NodeTraversal::next(*m_node) == m_pastEndNode;
-                Node* parentNode = m_node->parentOrShadowHostNode();
+                Node* parentNode = parentNodeOrShadowHost(m_behavior, *m_node);
                 while (!next && parentNode) {
                     if ((pastEnd && parentNode == m_endContainer) || m_endContainer->isDescendantOf(*parentNode))
                         return;
@@ -461,7 +524,7 @@
                     Node* exitedNode = m_node;
                     m_node = parentNode;
                     m_fullyClippedStack.pop();
-                    parentNode = m_node->parentOrShadowHostNode();
+                    parentNode = parentNodeOrShadowHost(m_behavior, *m_node);
                     if (haveRenderer)
                         exitNode(exitedNode);
                     if (m_positionNode) {
@@ -469,7 +532,7 @@
                         m_handledChildren = true;
                         return;
                     }
-                    next = m_node->nextSibling();
+                    next = nextSibling(m_behavior, *m_node);
                 }
             }
             m_fullyClippedStack.pop();            
@@ -2640,6 +2703,8 @@
 
     bool searchForward = !(options & Backwards);
     TextIteratorBehavior iteratorOptions = TextIteratorEntersTextControls | TextIteratorClipsToFrameAncestors;
+    if (!(options & DoNotTraverseFlatTree))
+        iteratorOptions |= TextIteratorTraversesFlatTree;
 
     CharacterIterator findIterator(range, iteratorOptions);
     auto result = findPlainTextOffset(buffer, findIterator, searchForward);

Modified: trunk/Source/WebCore/editing/TextIteratorBehavior.h (210431 => 210432)


--- trunk/Source/WebCore/editing/TextIteratorBehavior.h	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/Source/WebCore/editing/TextIteratorBehavior.h	2017-01-06 03:45:16 UTC (rev 210432)
@@ -57,6 +57,8 @@
     // Makes visiblity test take into account the visibility of the frame.
     // FIXME: This should probably be always on unless TextIteratorIgnoresStyleVisibility is set.
     TextIteratorClipsToFrameAncestors = 1 << 8,
+
+    TextIteratorTraversesFlatTree = 1 << 9,
 };
 
 typedef unsigned short TextIteratorBehavior;

Modified: trunk/Source/WebCore/page/DOMWindow.cpp (210431 => 210432)


--- trunk/Source/WebCore/page/DOMWindow.cpp	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/Source/WebCore/page/DOMWindow.cpp	2017-01-06 03:45:16 UTC (rev 210432)
@@ -1207,7 +1207,7 @@
 
     // FIXME (13016): Support wholeWord, searchInFrames and showDialog.    
     FindOptions options = (backwards ? Backwards : 0) | (caseSensitive ? 0 : CaseInsensitive) | (wrap ? WrapAround : 0);
-    return m_frame->editor().findString(string, options);
+    return m_frame->editor().findString(string, options | DoNotTraverseFlatTree);
 }
 
 bool DOMWindow::offscreenBuffering() const

Modified: trunk/Source/WebCore/testing/Internals.cpp (210431 => 210432)


--- trunk/Source/WebCore/testing/Internals.cpp	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/Source/WebCore/testing/Internals.cpp	2017-01-06 03:45:16 UTC (rev 210432)
@@ -1822,23 +1822,76 @@
     document->frame()->editor().toggleOverwriteModeEnabled();
 }
 
-unsigned Internals::countMatchesForText(const String& text, unsigned findOptions, const String& markMatches)
+static ExceptionOr<FindOptions> parseFindOptions(const Vector<String>& optionList)
 {
+    const struct {
+        const char* name;
+        FindOptionFlag value;
+    } flagList[] = {
+        {"CaseInsensitive", CaseInsensitive},
+        {"AtWordStarts", AtWordStarts},
+        {"TreatMedialCapitalAsWordStart", TreatMedialCapitalAsWordStart},
+        {"Backwards", Backwards},
+        {"WrapAround", WrapAround},
+        {"StartInSelection", StartInSelection},
+        {"DoNotRevealSelection", DoNotRevealSelection},
+        {"AtWordEnds", AtWordEnds},
+        {"DoNotTraverseFlatTree", DoNotTraverseFlatTree},
+    };
+    FindOptions result = 0;
+    for (auto& option : optionList) {
+        bool found = false;
+        for (auto& flag : flagList) {
+            if (flag.name == option) {
+                result |= flag.value;
+                found = true;
+                break;
+            }
+        }
+        if (!found)
+            return Exception { SYNTAX_ERR };
+    }
+    return result;
+}
+
+ExceptionOr<RefPtr<Range>> Internals::rangeOfString(const String& text, RefPtr<Range>&& referenceRange, const Vector<String>& findOptions)
+{
     Document* document = contextDocument();
     if (!document || !document->frame())
-        return 0;
+        return Exception { INVALID_ACCESS_ERR };
 
+    auto parsedOptions = parseFindOptions(findOptions);
+    if (parsedOptions.hasException())
+        return parsedOptions.releaseException();
+
+    return document->frame()->editor().rangeOfString(text, referenceRange.get(), parsedOptions.releaseReturnValue());
+}
+
+ExceptionOr<unsigned> Internals::countMatchesForText(const String& text, const Vector<String>& findOptions, const String& markMatches)
+{
+    Document* document = contextDocument();
+    if (!document || !document->frame())
+        return Exception { INVALID_ACCESS_ERR };
+
+    auto parsedOptions = parseFindOptions(findOptions);
+    if (parsedOptions.hasException())
+        return parsedOptions.releaseException();
+
     bool mark = markMatches == "mark";
-    return document->frame()->editor().countMatchesForText(text, nullptr, findOptions, 1000, mark, nullptr);
+    return document->frame()->editor().countMatchesForText(text, nullptr, parsedOptions.releaseReturnValue(), 1000, mark, nullptr);
 }
 
-unsigned Internals::countFindMatches(const String& text, unsigned findOptions)
+ExceptionOr<unsigned> Internals::countFindMatches(const String& text, const Vector<String>& findOptions)
 {
     Document* document = contextDocument();
     if (!document || !document->page())
-        return 0;
+        return Exception { INVALID_ACCESS_ERR };
 
-    return document->page()->countFindMatches(text, findOptions, 1000);
+    auto parsedOptions = parseFindOptions(findOptions);
+    if (parsedOptions.hasException())
+        return parsedOptions.releaseException();
+
+    return document->page()->countFindMatches(text, parsedOptions.releaseReturnValue(), 1000);
 }
 
 unsigned Internals::numberOfLiveNodes() const

Modified: trunk/Source/WebCore/testing/Internals.h (210431 => 210432)


--- trunk/Source/WebCore/testing/Internals.h	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/Source/WebCore/testing/Internals.h	2017-01-06 03:45:16 UTC (rev 210432)
@@ -237,8 +237,9 @@
     bool isOverwriteModeEnabled();
     void toggleOverwriteModeEnabled();
 
-    unsigned countMatchesForText(const String&, unsigned findOptions, const String& markMatches);
-    unsigned countFindMatches(const String&, unsigned findOptions);
+    ExceptionOr<RefPtr<Range>> rangeOfString(const String&, RefPtr<Range>&&, const Vector<String>& findOptions);
+    ExceptionOr<unsigned> countMatchesForText(const String&, const Vector<String>& findOptions, const String& markMatches);
+    ExceptionOr<unsigned> countFindMatches(const String&, const Vector<String>& findOptions);
 
     unsigned numberOfScrollableAreas();
 

Modified: trunk/Source/WebCore/testing/Internals.idl (210431 => 210432)


--- trunk/Source/WebCore/testing/Internals.idl	2017-01-06 03:30:50 UTC (rev 210431)
+++ trunk/Source/WebCore/testing/Internals.idl	2017-01-06 03:45:16 UTC (rev 210432)
@@ -178,9 +178,11 @@
     void setEditingValue(HTMLInputElement inputElement, DOMString value);
     void setAutofilled(HTMLInputElement inputElement, boolean enabled);
     void setShowAutoFillButton(HTMLInputElement inputElement, AutoFillButtonType autoFillButtonType);
-    unsigned long countMatchesForText(DOMString text, unsigned long findOptions, DOMString markMatches);
-    unsigned long countFindMatches(DOMString text, unsigned long findOptions);
 
+    [MayThrowException] Range? rangeOfString(DOMString text, Range? referenceRange, sequence<DOMString> findOptions);
+    [MayThrowException] unsigned long countMatchesForText(DOMString text, sequence<DOMString> findOptions, DOMString markMatches);
+    [MayThrowException] unsigned long countFindMatches(DOMString text, sequence<DOMString> findOptions);
+
     [MayThrowException] DOMString autofillFieldName(Element formControlElement);
 
     [MayThrowException] void paintControlTints();
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to