Modified: trunk/LayoutTests/ChangeLog (103043 => 103044)
--- trunk/LayoutTests/ChangeLog 2011-12-16 08:51:18 UTC (rev 103043)
+++ trunk/LayoutTests/ChangeLog 2011-12-16 09:04:50 UTC (rev 103044)
@@ -1,3 +1,19 @@
+2011-12-16 Shinya Kawanaka <shin...@google.com>
+
+ A test that mutation happens when asynchronous spell checking is in process.
+ https://bugs.webkit.org/show_bug.cgi?id=72940
+
+ Reviewed by Hajime Morita.
+
+ Added a test that mutation happens when spellchecking.
+ This test confirms crash won't happen, and how markers are used.
+
+ * editing/spelling/spellcheck-async-mutation-expected.txt: Added.
+ * editing/spelling/spellcheck-async-mutation.html: Added.
+ * platform/gtk/Skipped:
+ * platform/mac-leopard/Skipped:
+ * platform/qt/Skipped:
+
2011-12-16 Hajime Morrita <morr...@chromium.org>
Marking some as fail.
Added: trunk/LayoutTests/editing/spelling/spellcheck-async-mutation-expected.txt (0 => 103044)
--- trunk/LayoutTests/editing/spelling/spellcheck-async-mutation-expected.txt (rev 0)
+++ trunk/LayoutTests/editing/spelling/spellcheck-async-mutation-expected.txt 2011-12-16 09:04:50 UTC (rev 103044)
@@ -0,0 +1,45 @@
+Test for asynchronous spellchecking in case DOM mutation happens. This test checks crash won't happen if DOM mutations happened.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
+Test Start: 1
+PASS requestId is >= lastRequestId + 1
+PASS Request has been processed.
+
+Test Start: 2
+PASS requestId is >= lastRequestId + 1
+PASS Request has been processed.
+
+Test Start: 3
+PASS requestId is >= lastRequestId + 1
+PASS Request has been processed.
+
+Test Start: 4
+PASS requestId is >= lastRequestId + 1
+PASS Request has been processed.
+
+Test Start: 5
+PASS requestId is >= lastRequestId + 1
+PASS Request has been processed.
+
+Test Start: 6
+PASS requestId is >= lastRequestId + 1
+PASS Request has been processed.
+
+Test Start: 7
+PASS requestId is >= lastRequestId + 1
+PASS Request has been processed.
+
+Test Start: 8
+PASS requestId is >= lastRequestId + 1
+PASS Request has been processed.
+
+Test Start: 9
+PASS requestId is >= lastRequestId + 1
+PASS Request has been processed.
+zz zz zz
+
Added: trunk/LayoutTests/editing/spelling/spellcheck-async-mutation.html (0 => 103044)
--- trunk/LayoutTests/editing/spelling/spellcheck-async-mutation.html (rev 0)
+++ trunk/LayoutTests/editing/spelling/spellcheck-async-mutation.html 2011-12-16 09:04:50 UTC (rev 103044)
@@ -0,0 +1,243 @@
+<!DOCTYPE html>
+<html>
+<head>
+<link rel="stylesheet" href=""
+<script src=""
+<script src=""
+<style>
+.editing {
+ border: 2px solid red;
+ padding: 6px;
+ font-size: 18px;
+}
+</style>
+</head>
+<body>
+<pre id="description"></pre>
+<pre id="console"></pre>
+
+<div id="source">zz zz zz</textarea>
+
+<div id="container"></div>
+
+<div id="move-target"></div>
+
+<script>
+description(
+ "Test for asynchronous spellchecking in case DOM mutation happens. " +
+ "This test checks crash won't happen if DOM mutations happened."
+);
+
+if (window.layoutTestController) {
+ layoutTestController.dumpAsText();
+ layoutTestController.setAsynchronousSpellCheckingEnabled(true);
+ layoutTestController.waitUntilDone();
+}
+
+if (window.internals)
+ internals.setUnifiedTextCheckingEnabled(document, true);
+
+var sourceIds = ['source'];
+var destElems = ['textarea', 'input', 'contenteditable'];
+var tweaks = ['delete', 'move', 'mutate'];
+
+var testData = [];
+for (var i = 0; i < sourceIds.length; ++i) {
+ for (var j = 0; j < destElems.length; ++j) {
+ for (var k = 0; k < tweaks.length; ++k) {
+ testData.push({
+ sourceId: sourceIds[i],
+ destElem: destElems[j],
+ tweak: tweaks[k]
+ });
+ }
+ }
+}
+
+var sel = window.getSelection();
+
+function removeAllChildren(elem) {
+ while (elem.firstChild)
+ elem.removeChild(elem.firstChild);
+}
+
+var testNo = 0;
+function doTestIfAny() {
+ // Clean up first.
+ removeAllChildren(document.getElementById('container'));
+ removeAllChildren(document.getElementById('move-target'));
+
+ var next = testData.shift();
+ if (next)
+ return window.setTimeout(function(){ doTest(++testNo, next); }, 0);
+
+ // No more tests. Tear down.
+ removeAllChildren(document.getElementById('container'));
+ removeAllChildren(document.getElementById('move-target'));
+
+ if (window.internals)
+ internals.setUnifiedTextCheckingEnabled(document, false);
+
+ if (window.layoutTestController) {
+ layoutTestController.setAsynchronousSpellCheckingEnabled(false);
+ layoutTestController.notifyDone();
+ }
+}
+
+var requestId;
+var lastRequestId;
+function doTest(testNo, testData) {
+ function createElement(kind) {
+ if (kind == 'textarea' || kind == 'input')
+ return document.createElement(kind);
+
+ var div = document.createElement('div');
+ div.setAttribute('contenteditable', true);
+ return div;
+ }
+
+ debug("");
+ debug("Test Start: " + testNo);
+
+ var source = document.getElementById(testData.sourceId);
+ var destination = createElement(testData.destElem);
+ document.getElementById('container').appendChild(destination);
+
+ if (window.internals)
+ lastRequestId = internals.lastSpellCheckRequestSequence(document);
+
+ // A spellcheck request will be invoked.
+ doCopyAndPaste(source, destination);
+
+ setTimeout(function() {
+ if (window.internals)
+ requestId = internals.lastSpellCheckRequestSequence(document);
+ shouldBeGreaterThanOrEqual('requestId', 'lastRequestId + 1');
+
+ // Then, tweak
+ tweak(destination, testData.tweak);
+
+ waitForSpellCheckRequestDone(requestId, destination, testData.tweak, 10, 1);
+ }, 0);
+}
+
+function doCopyAndPaste(source, dest) {
+ function focusOn(elem) {
+ if (elem instanceof HTMLInputElement || elem instanceof HTMLTextAreaElement)
+ elem.focus();
+ else
+ sel.selectAllChildren(elem);
+ }
+
+ sel.selectAllChildren(source);
+ document.execCommand("Copy");
+
+ focusOn(dest);
+ document.execCommand("Paste");
+}
+
+function tweak(elem, kind) {
+ switch (kind) {
+ case 'delete':
+ elem.parentNode.removeChild(elem);
+ return;
+ case 'move':
+ var target = document.getElementById('move-target');
+ target.appendChild(elem);
+ return;
+ case 'mutate':
+ if (elem instanceof HTMLInputElement || elem instanceof HTMLTextAreaElement)
+ elem.value = 'zzz';
+ else
+ elem.innerHTML = 'zzz';
+ return;
+ default:
+ testFailed('Unknown kind of tweak');
+ return;
+ }
+}
+
+function waitForSpellCheckRequestDone(requestId, destination, tweakKind, restTry, nsleep) {
+ // No more try.
+ if (restTry <= 0) {
+ testFailed('Failed verification');
+ setTimeout(doTestIfAny, 0);
+ return;
+ }
+
+ if (window.internals)
+ var lastProcessedId = internals.lastSpellCheckProcessedSequence(document);
+
+ if (requestId != lastProcessedId) {
+ setTimeout(function() {
+ waitForSpellCheckRequestDone(requestId, destination, tweakKind, restTry - 1, nsleep * 2);
+ }, nsleep);
+ return;
+ }
+
+ if (verifyExistenceOfMarkers(destination, tweakKind)) {
+ testPassed('Request has been processed.');
+ } else {
+ testFailed('Request has been processed but we detected unexpected marker location.');
+ }
+
+ setTimeout(doTestIfAny, 0);
+}
+
+function findFirstTextNode(node)
+{
+ function iterToFindFirstTextNode(node)
+ {
+ if (node instanceof Text)
+ return node;
+
+ var childNodes = node.childNodes;
+ for (var i = 0; i < childNodes.length; ++i) {
+ var n = iterToFindFirstTextNode(childNodes[i]);
+ if (n)
+ return n;
+ }
+
+ return null;
+ }
+
+ if (node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement)
+ return iterToFindFirstTextNode(internals.shadowRoot(node));
+ else
+ return iterToFindFirstTextNode(node);
+}
+
+function verifyExistenceOfMarkers(elem, tweakKind) {
+ if (!window.internals)
+ return true;
+
+ switch (tweakKind) {
+ case 'delete':
+ return true;
+ case 'move':
+ // In move, marker should be there.
+ var markerNum = internals.markerCountForNode(findFirstTextNode(elem), "spelling");
+ if (markerNum != 3)
+ return false;
+ for (var i = 0; i < 3; ++i) {
+ var range = internals.markerRangeForNode(findFirstTextNode(elem), "spelling", i);
+ if (range.toString() != "zz")
+ return false;
+ }
+ return true;
+ case 'mutate':
+ // In mutation, there aren't markers.
+ return internals.markerCountForNode(findFirstTextNode(elem), "spelling") == 0;
+ default:
+ testFailed('Unknown kind of tweak');
+ return true;
+ }
+}
+
+doTestIfAny();
+
+var successfullyParsed = true;
+</script>
+<script src=""
+</body>
+</html>
Modified: trunk/LayoutTests/platform/gtk/Skipped (103043 => 103044)
--- trunk/LayoutTests/platform/gtk/Skipped 2011-12-16 08:51:18 UTC (rev 103043)
+++ trunk/LayoutTests/platform/gtk/Skipped 2011-12-16 09:04:50 UTC (rev 103044)
@@ -1220,6 +1220,7 @@
editing/spelling/spellcheck-paste.html
editing/spelling/spellcheck-queue.html
editing/spelling/spellcheck-sequencenum.html
+editing/spelling/spellcheck-async-mutation.html
# For https://bugs.webkit.org/show_bug.cgi?id=50758
# These require DRT setSerializeHTTPLoads implementation to be reliable.
Modified: trunk/LayoutTests/platform/mac-leopard/Skipped (103043 => 103044)
--- trunk/LayoutTests/platform/mac-leopard/Skipped 2011-12-16 08:51:18 UTC (rev 103043)
+++ trunk/LayoutTests/platform/mac-leopard/Skipped 2011-12-16 09:04:50 UTC (rev 103044)
@@ -96,6 +96,7 @@
# Available 10.6 or later
editing/spelling/spellcheck-paste.html
+editing/spelling/spellcheck-async-mutation.html
editing/spelling/grammar-paste.html
fast/html/set-text-direction.html
Modified: trunk/LayoutTests/platform/qt/Skipped (103043 => 103044)
--- trunk/LayoutTests/platform/qt/Skipped 2011-12-16 08:51:18 UTC (rev 103043)
+++ trunk/LayoutTests/platform/qt/Skipped 2011-12-16 09:04:50 UTC (rev 103044)
@@ -1007,6 +1007,7 @@
editing/spelling/spellcheck-paste.html
editing/spelling/spellcheck-queue.html
editing/spelling/spellcheck-sequencenum.html
+editing/spelling/spellcheck-async-mutation.html
# [Qt][GTK] editing/spelling/spellcheck-async.html fails
# https://bugs.webkit.org/show_bug.cgi?id=73003