Source/WebCore/ChangeLog

 12011-12-04 Shinya Kawanaka <shinyak@google.com>
 2
 3 Asynchronous SpellChecker should consider multiple requests.
 4 https://bugs.webkit.org/show_bug.cgi?id=72939
 5
 6 Reviewed by NOBODY (OOPS!).
 7
 8 Now SpellChecker saves a request when it is processing the previous spellcheck request.
 9 If there is a request having the same root editable element, the older request is replaced by newer request
 10
 11 Test: editing/spelling/spellcheck-queue.html
 12
 13 * editing/SpellChecker.cpp:
 14 (WebCore::SpellChecker::SpellCheckRequest::SpellCheckRequest):
 15 A structure to have spell check request.
 16 (WebCore::SpellChecker::SpellCheckRequest::sequence):
 17 (WebCore::SpellChecker::SpellCheckRequest::range):
 18 (WebCore::SpellChecker::SpellCheckRequest::text):
 19 (WebCore::SpellChecker::SpellCheckRequest::mask):
 20 (WebCore::SpellChecker::SpellCheckRequest::rootEditableElement):
 21 (WebCore::SpellChecker::SpellChecker):
 22 (WebCore::SpellChecker::createRequest):
 23 (WebCore::SpellChecker::timerFiredToProcessQueuedRequest):
 24 When timer is fired, queued request is processed if any.
 25 (WebCore::SpellChecker::canCheckAsynchronously):
 26 (WebCore::SpellChecker::requestCheckingFor):
 27 When the spellchecker is processing another request, the latest request is queued.
 28 (WebCore::SpellChecker::invokeRequest):
 29 (WebCore::SpellChecker::enqueueRequest):
 30 Enqueues a request. If there is an older request whose root editable element is the same as the request,
 31 it will be replaced.
 32 (WebCore::SpellChecker::didCheck):
 33 * editing/SpellChecker.h:
 34
1352011-12-04 Ryosuke Niwa <rniwa@webkit.org>
236
337 HIERARCHY_REQUEST_ERR check in checkAcceptChild should be optimized for newChild without children

Source/WebCore/editing/SpellChecker.cpp

4545
4646namespace WebCore {
4747
 48class SpellChecker::SpellCheckRequest : public RefCounted<SpellChecker::SpellCheckRequest> {
 49public:
 50 SpellCheckRequest(int sequence, PassRefPtr<Range> range, const String& text, TextCheckingTypeMask mask)
 51 : m_sequence(sequence)
 52 , m_range(range)
 53 , m_text(text)
 54 , m_mask(mask)
 55 , m_rootEditableElement(m_range->startContainer()->rootEditableElement())
 56 {
 57 }
 58
 59 int sequence() const { return m_sequence; }
 60 Range* range() const { return m_range.get(); }
 61 const String& text() const { return m_text; }
 62 TextCheckingTypeMask mask() const { return m_mask; }
 63 Element* rootEditableElement() const { return m_rootEditableElement; }
 64
 65private:
 66 int m_sequence;
 67 RefPtr<Range> m_range;
 68 String m_text;
 69 TextCheckingTypeMask m_mask;
 70 Element* m_rootEditableElement;
 71};
 72
4873SpellChecker::SpellChecker(Frame* frame)
4974 : m_frame(frame)
50  , m_requestSequence(0)
 75 , m_lastRequestedSequence(0)
 76 , m_timerToProcessQueuedRequest(this, &SpellChecker::timerFiredToProcessQueuedRequest)
5177{
5278}
5379

@@TextCheckerClient* SpellChecker::client() const
6389 return page->editorClient()->textChecker();
6490}
6591
66 bool SpellChecker::initRequest(PassRefPtr<Range> range)
 92PassRefPtr<SpellChecker::SpellCheckRequest> SpellChecker::createRequest(TextCheckingTypeMask mask, PassRefPtr<Range> range)
6793{
6894 ASSERT(canCheckAsynchronously(range.get()));
6995
7096 String text = range->text();
7197 if (!text.length())
72  return false;
 98 return PassRefPtr<SpellCheckRequest>();
7399
74  m_requestRange = range;
75  m_requestText = text;
76  m_requestSequence++;
77 
78  return true;
 100 return adoptRef(new SpellCheckRequest(++m_lastRequestedSequence, range, text, mask));
79101}
80102
81 void SpellChecker::clearRequest()
 103void SpellChecker::timerFiredToProcessQueuedRequest(Timer<SpellChecker>*)
82104{
83  m_requestRange.clear();
84  m_requestText = String();
 105 ASSERT(!m_requestQueue.isEmpty());
 106 if (m_requestQueue.isEmpty())
 107 return;
 108
 109 invokeRequest(m_requestQueue.takeFirst());
85110}
86111
87112bool SpellChecker::isAsynchronousEnabled() const

@@bool SpellChecker::isAsynchronousEnabled() const
91116
92117bool SpellChecker::canCheckAsynchronously(Range* range) const
93118{
94  return client() && isCheckable(range) && isAsynchronousEnabled() && !isBusy();
95 }
96 
97 bool SpellChecker::isBusy() const
98 {
99  return m_requestRange.get();
100 }
101 
102 bool SpellChecker::isValid(int sequence) const
103 {
104  return m_requestRange.get() && m_requestText.length() && m_requestSequence == sequence;
 119 return client() && isCheckable(range) && isAsynchronousEnabled();
105120}
106121
107122bool SpellChecker::isCheckable(Range* range) const

@@void SpellChecker::requestCheckingFor(TextCheckingTypeMask mask, PassRefPtr<Rang
114129 if (!canCheckAsynchronously(range.get()))
115130 return;
116131
117  doRequestCheckingFor(mask, range);
 132 RefPtr<SpellCheckRequest> request(createRequest(mask, range));
 133 if (!request)
 134 return;
 135
 136 if (m_timerToProcessQueuedRequest.isActive() || m_processingRequest) {
 137 enqueueRequest(request.release());
 138 return;
 139 }
 140
 141 invokeRequest(request.release());
118142}
119143
120 void SpellChecker::doRequestCheckingFor(TextCheckingTypeMask mask, PassRefPtr<Range> range)
 144void SpellChecker::invokeRequest(PassRefPtr<SpellCheckRequest> request)
121145{
122  ASSERT(canCheckAsynchronously(range.get()));
 146 ASSERT(!m_processingRequest);
 147
 148 m_processingRequest = request;
 149 client()->requestCheckingOfString(this, m_processingRequest->sequence(), m_processingRequest->mask(), m_processingRequest->text());
 150}
123151
124  if (!initRequest(range))
 152void SpellChecker::enqueueRequest(PassRefPtr<SpellCheckRequest> request)
 153{
 154 ASSERT(request);
 155
 156 for (RequestQueue::iterator it = m_requestQueue.begin(); it != m_requestQueue.end(); ++it) {
 157 if (request->rootEditableElement() != (*it)->rootEditableElement())
 158 continue;
 159
 160 *it = request;
125161 return;
126  client()->requestCheckingOfString(this, m_requestSequence, mask, m_requestText);
 162 }
 163
 164 m_requestQueue.append(request);
127165}
128166
129167static bool forwardIterator(PositionIterator& iterator, int distance)

@@static DocumentMarker::MarkerType toMarkerType(TextCheckingType type)
159197// Currenntly ignoring TextCheckingResult::details but should be handled. See Bug 56368.
160198void SpellChecker::didCheck(int sequence, const Vector<TextCheckingResult>& results)
161199{
162  if (!isValid(sequence))
163  return;
 200 ASSERT(m_processingRequest);
164201
165  if (!isCheckable(m_requestRange.get())) {
166  clearRequest();
 202 ASSERT(m_processingRequest->sequence() == sequence);
 203 if (m_processingRequest->sequence() != sequence) {
 204 m_requestQueue.clear();
167205 return;
168206 }
169207
170208 int startOffset = 0;
171  PositionIterator start = m_requestRange->startPosition();
 209 PositionIterator start = m_processingRequest->range()->startPosition();
172210 for (size_t i = 0; i < results.size(); ++i) {
173211 if (results[i].type != TextCheckingTypeSpelling && results[i].type != TextCheckingTypeGrammar)
174212 continue;

@@void SpellChecker::didCheck(int sequence, const Vector<TextCheckingResult>& resu
186224 // spellings in the background. To avoid adding markers to the words modified by users or
187225 // JavaScript applications, retrieve the words in the specified region and compare them with
188226 // the original ones.
189  RefPtr<Range> range = Range::create(m_requestRange->ownerDocument(), start, end);
 227 RefPtr<Range> range = Range::create(m_processingRequest->range()->ownerDocument(), start, end);
190228 // FIXME: Use textContent() compatible string conversion.
191229 String destination = range->text();
192  String source = m_requestText.substring(results[i].location, results[i].length);
 230 String source = m_processingRequest->text().substring(results[i].location, results[i].length);
193231 if (destination == source)
194  m_requestRange->ownerDocument()->markers()->addMarker(range.get(), toMarkerType(results[i].type));
 232 m_processingRequest->range()->ownerDocument()->markers()->addMarker(range.get(), toMarkerType(results[i].type));
195233
196234 startOffset = results[i].location;
197235 }
198236
199  clearRequest();
 237 m_processingRequest.clear();
 238 if (!m_requestQueue.isEmpty())
 239 m_timerToProcessQueuedRequest.startOneShot(0);
200240}
201241
202242

Source/WebCore/editing/SpellChecker.h

2828
2929#include "PlatformString.h"
3030#include "TextChecking.h"
 31#include "Timer.h"
 32#include <wtf/Deque.h>
3133#include <wtf/RefPtr.h>
3234#include <wtf/Noncopyable.h>
3335#include <wtf/Vector.h>

@@public:
4749 ~SpellChecker();
4850
4951 bool isAsynchronousEnabled() const;
50  bool canCheckAsynchronously(Range*) const;
51  bool isBusy() const;
52  bool isValid(int sequence) const;
5352 bool isCheckable(Range*) const;
5453 void requestCheckingFor(TextCheckingTypeMask, PassRefPtr<Range>);
5554 void didCheck(int sequence, const Vector<TextCheckingResult>&);
5655
5756private:
58  bool initRequest(PassRefPtr<Range>);
59  void clearRequest();
60  void doRequestCheckingFor(TextCheckingTypeMask, PassRefPtr<Range>);
 57 class SpellCheckRequest;
 58 typedef Deque<RefPtr<SpellCheckRequest> > RequestQueue;
 59
 60 bool canCheckAsynchronously(Range*) const;
 61 PassRefPtr<SpellCheckRequest> createRequest(TextCheckingTypeMask, PassRefPtr<Range>);
6162 TextCheckerClient* client() const;
 63 void timerFiredToProcessQueuedRequest(Timer<SpellChecker>*);
 64 void invokeRequest(PassRefPtr<SpellCheckRequest>);
 65 void enqueueRequest(PassRefPtr<SpellCheckRequest>);
6266
6367 Frame* m_frame;
 68 int m_lastRequestedSequence;
 69
 70 Timer<SpellChecker> m_timerToProcessQueuedRequest;
6471
65  RefPtr<Range> m_requestRange;
66  String m_requestText;
67  int m_requestSequence;
 72 RefPtr<SpellCheckRequest> m_processingRequest;
 73 RequestQueue m_requestQueue;
6874};
6975
7076} // namespace WebCore

LayoutTests/ChangeLog

 12011-12-04 Shinya Kawanaka <shinyak@google.com>
 2
 3 Asynchronous SpellChecker should consider multiple requests.
 4 https://bugs.webkit.org/show_bug.cgi?id=72939
 5
 6 Reviewed by NOBODY (OOPS!).
 7
 8 Tests for multiple spellcheck requests.
 9
 10 * editing/spelling/spellcheck-queue-expected.txt: Added.
 11 * editing/spelling/spellcheck-queue.html: Added.
 12 * platform/gtk/Skipped:
 13 * platform/qt/Skipped:
 14
1152011-12-04 Darin Adler <darin@apple.com>
216
317 Removed obsolete expected results that should have been removed when converting to a ref test.

LayoutTests/editing/spelling/spellcheck-queue-expected.txt

 1For Bug 72939: Asynchronous SpellChecker should consider multiple requests.
 2
 3On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 4
 5
 6PASS successfullyParsed is true
 7
 8TEST COMPLETE
 9PASS INPUT has a marker on 'zz apple orange'
 10PASS TEXTAREA has a marker on 'zz apple orange'
 11PASS DIV has a marker on 'zz apple orange'
 12PASS INPUT has a marker on 'zz apple orange'
 13PASS TEXTAREA has a marker on 'zz apple orange'
 14PASS DIV has a marker on 'zz apple orange'
 15PASS INPUT has a marker on 'zz apple orange'
 16PASS TEXTAREA has a marker on 'zz apple orange'
 17PASS DIV has a marker on 'zz apple orange'
 18

LayoutTests/editing/spelling/spellcheck-queue.html

 1<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
 2<html>
 3<head>
 4<script src="../../fast/js/resources/js-test-pre.js"></script>
 5<script src="resources/js-test-selection-shared.js"></script>
 6</head>
 7<body>
 8<p id="description"></p>
 9<div id="console"></div>
 10<script>
 11description('For Bug 72939: Asynchronous SpellChecker should consider multiple requests.');
 12
 13if (window.layoutTestController) {
 14 layoutTestController.waitUntilDone();
 15 layoutTestController.setAsynchronousSpellCheckingEnabled(true);
 16}
 17
 18var testRoot = document.createElement("div");
 19document.body.insertBefore(testRoot, document.body.firstChild);
 20
 21var source1 = document.createElement("div");
 22source1.innerHTML = "foo bar";
 23testRoot.appendChild(source1);
 24
 25var source2 = document.createElement("div");
 26source2.innerHTML = "zz apple orange";
 27testRoot.appendChild(source2);
 28
 29function createInput(testRoot) {
 30 var e = document.createElement('input');
 31 e.setAttribute("type", "text");
 32 testRoot.appendChild(e);
 33
 34 return e;
 35}
 36
 37function createTextArea(testRoot) {
 38 var e = document.createElement("textarea");
 39 testRoot.appendChild(e);
 40
 41 return e;
 42}
 43
 44function createContentEditable(testRoot) {
 45 var e = document.createElement("div");
 46 e.setAttribute("contentEditable", "true");
 47 testRoot.appendChild(e);
 48
 49 return e;
 50}
 51
 52var destinations = [
 53 createInput(testRoot),
 54 createTextArea(testRoot),
 55 createContentEditable(testRoot),
 56 createInput(testRoot),
 57 createTextArea(testRoot),
 58 createContentEditable(testRoot),
 59 createInput(testRoot),
 60 createTextArea(testRoot),
 61 createContentEditable(testRoot),
 62];
 63
 64var sel = window.getSelection();
 65
 66var tests = [];
 67for (var i = 0; i < destinations.length; ++i) {
 68 var t = function(i) {
 69 return function() { verify(source2, destinations[i], ["zz"]); }
 70 }(i);
 71 tests.push(t);
 72}
 73
 74function verifyIfAny()
 75{
 76 var next = tests.shift();
 77 if (next) {
 78 next();
 79 return;
 80 }
 81
 82 testRoot.style.display = "none";
 83 if (window.layoutTestController) {
 84 layoutTestController.setAsynchronousSpellCheckingEnabled(false);
 85 layoutTestController.notifyDone();
 86 }
 87}
 88
 89function findFirstTextNode(node)
 90{
 91 function iterToFindFirstTextNode(node)
 92 {
 93 if (node instanceof Text)
 94 return node;
 95
 96 var childNodes = node.childNodes;
 97 for (var i = 0; i < childNodes.length; ++i) {
 98 var n = iterToFindFirstTextNode(childNodes[i]);
 99 if (n)
 100 return n;
 101 }
 102
 103 return null;
 104 }
 105
 106
 107 if (node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement)
 108 return iterToFindFirstTextNode(internals.shadowRoot(node));
 109 else
 110 return iterToFindFirstTextNode(node);
 111}
 112
 113function verifyMarker(node, expectedMarked)
 114{
 115 if (!window.layoutTestController || !window.internals)
 116 return false;
 117
 118 var textNode = findFirstTextNode(node);
 119
 120 var num = internals.markerCountForNode(textNode, "spelling");
 121 if (num != expectedMarked.length)
 122 return false;
 123 for (var i = 0; i < num; ++i) {
 124 var range = internals.markerRangeForNode(textNode, "spelling", i);
 125 if (range.toString() != expectedMarked[i])
 126 return false;
 127 }
 128
 129 return true;
 130}
 131
 132function copyAndPaste(source, dest)
 133{
 134 sel.selectAllChildren(source);
 135 document.execCommand("Copy");
 136
 137 if (dest instanceof HTMLInputElement || dest instanceof HTMLTextAreaElement) {
 138 dest.value = "";
 139 dest.focus();
 140 } else {
 141 dest.innerHTML = "";
 142 sel.selectAllChildren(dest);
 143 }
 144 document.execCommand("Paste");
 145}
 146
 147function verify(source, dest, expectedMarked)
 148{
 149 var nretry = 10;
 150 var nsleep = 1;
 151 function trial() {
 152 var verified = verifyMarker(dest, expectedMarked);
 153 if (verified) {
 154 testPassed(dest.tagName + " has a marker on '" + source.innerHTML + "'");
 155 verifyIfAny();
 156 return;
 157 }
 158
 159 nretry--;
 160 if (0 == nretry) {
 161 testFailed(dest.tagName + " should have a marker on for '" + source.innerHTML + "'");
 162 verifyIfAny();
 163 return;
 164 }
 165
 166 nsleep *= 2;
 167 window.setTimeout(trial, nsleep);
 168 };
 169 trial();
 170}
 171
 172
 173// paste "foo bar"
 174for (var i = 0; i < destinations.length; ++i)
 175 copyAndPaste(source1, destinations[i]);
 176
 177// paste "zz apple orange"
 178for (var i = 0; i < destinations.length; ++i)
 179 copyAndPaste(source2, destinations[i]);
 180
 181verifyIfAny();
 182
 183var successfullyParsed = true;
 184
 185</script>
 186<script src="../../fast/js/resources/js-test-post.js"></script>
 187</body>
 188</html>

LayoutTests/platform/gtk/Skipped

@@media/event-attributes.html
12151215# https://bugs.webkit.org/show_bug.cgi?id=50740
12161216editing/spelling/spelling-backspace-between-lines.html
12171217editing/spelling/spellcheck-paste.html
 1218editing/spelling/spellcheck-queue.html
12181219
12191220# For https://bugs.webkit.org/show_bug.cgi?id=50758
12201221# These require DRT setSerializeHTTPLoads implementation to be reliable.

LayoutTests/platform/qt/Skipped

@@editing/spelling/spelling-attribute-at-child.html
10051005
10061006# EditorClient::requestCheckingOfString() is not implemented
10071007editing/spelling/spellcheck-paste.html
 1008editing/spelling/spellcheck-queue.html
10081009
10091010# [Qt][GTK] editing/spelling/spellcheck-async.html fails
10101011# https://bugs.webkit.org/show_bug.cgi?id=73003