/* * Copyright (C) 2010 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "SpellChecker.h" #include "Document.h" #include "DocumentMarkerController.h" #include "EditorClient.h" #include "Frame.h" #include "HTMLInputElement.h" #include "HTMLTextAreaElement.h" #include "Node.h" #include "Page.h" #include "PositionIterator.h" #include "Range.h" #include "RenderObject.h" #include "Settings.h" #include "TextCheckerClient.h" #include "TextIterator.h" #include "htmlediting.h" namespace WebCore { SpellChecker::SpellChecker(Frame* frame) : m_frame(frame) , m_requestSequence(0) { } SpellChecker::~SpellChecker() { } TextCheckerClient* SpellChecker::client() const { Page* page = m_frame->page(); if (!page) return 0; return page->editorClient()->textChecker(); } bool SpellChecker::initRequest(Node* node) { ASSERT(canCheckAsynchronously(node)); String text = node->textContent(); if (!text.length()) return false; m_requestNode = node; m_requestText = text; m_requestSequence++; return true; } void SpellChecker::clearRequest() { m_requestNode.clear(); m_requestText = String(); } bool SpellChecker::isAsynchronousEnabled() const { return m_frame->settings() && m_frame->settings()->asynchronousSpellCheckingEnabled(); } bool SpellChecker::canCheckAsynchronously(Node* node) const { return client() && isCheckable(node) && isAsynchronousEnabled() && !isBusy(); } bool SpellChecker::isBusy() const { return m_requestNode.get(); } bool SpellChecker::isValid(int sequence) const { return m_requestNode.get() && m_requestText.length() && m_requestSequence == sequence; } bool SpellChecker::isCheckable(Node* node) const { return node && node->renderer(); } void SpellChecker::requestCheckingFor(TextCheckingTypeMask mask, Node* node) { ASSERT(canCheckAsynchronously(node)); if (!initRequest(node)) return; client()->requestCheckingOfString(this, m_requestSequence, mask, m_requestText); } static bool forwardIterator(PositionIterator& iterator, int distance) { int remaining = distance; while (!iterator.atEnd()) { if (iterator.node()->isCharacterDataNode()) { int length = lastOffsetForEditing(iterator.node()); int last = length - iterator.offsetInLeafNode(); if (remaining < last) { iterator.setOffsetInLeafNode(iterator.offsetInLeafNode() + remaining); return true; } remaining -= last; iterator.setOffsetInLeafNode(iterator.offsetInLeafNode() + last); } iterator.increment(); } return false; } static DocumentMarker::MarkerType toMarkerType(TextCheckingType type) { if (type == TextCheckingTypeSpelling) return DocumentMarker::Spelling; ASSERT(type == TextCheckingTypeGrammar); return DocumentMarker::Grammar; } // Currenntly ignoring TextCheckingResult::details but should be handled. See Bug 56368. void SpellChecker::didCheck(int sequence, const Vector& results) { if (!isValid(sequence)) return; if (!m_requestNode->renderer()) { clearRequest(); return; } int startOffset = 0; PositionIterator start = firstPositionInOrBeforeNode(m_requestNode.get()); for (size_t i = 0; i < results.size(); ++i) { if (results[i].type != TextCheckingTypeSpelling && results[i].type != TextCheckingTypeGrammar) continue; // To avoid moving the position backward, we assume the given results are sorted with // startOffset as the ones returned by [NSSpellChecker requestCheckingOfString:]. ASSERT(startOffset <= results[i].location); if (!forwardIterator(start, results[i].location - startOffset)) break; PositionIterator end = start; if (!forwardIterator(end, results[i].length)) break; // Users or JavaScript applications may change text while a spell-checker checks its // spellings in the background. To avoid adding markers to the words modified by users or // JavaScript applications, retrieve the words in the specified region and compare them with // the original ones. RefPtr range = Range::create(m_requestNode->document(), start, end); // FIXME: Use textContent() compatible string conversion. String destination = range->text(); String source = m_requestText.substring(results[i].location, results[i].length); if (destination == source) m_requestNode->document()->markers()->addMarker(range.get(), toMarkerType(results[i].type)); startOffset = results[i].location; } clearRequest(); } } // namespace WebCore