| /* |
| * 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 "core/editing/spellcheck/SpellCheckRequester.h" |
| |
| #include "core/dom/Document.h" |
| #include "core/dom/Node.h" |
| #include "core/editing/EditingUtilities.h" |
| #include "core/editing/markers/DocumentMarkerController.h" |
| #include "core/editing/spellcheck/SpellChecker.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/Settings.h" |
| #include "platform/text/TextCheckerClient.h" |
| |
| namespace blink { |
| |
| SpellCheckRequest::SpellCheckRequest( |
| Range* checkingRange, |
| const String& text, |
| TextCheckingProcessType processType, |
| const Vector<uint32_t>& documentMarkersInRange, |
| const Vector<unsigned>& documentMarkerOffsets, |
| int requestNumber) |
| : m_requester(nullptr), |
| m_checkingRange(checkingRange), |
| m_rootEditableElement( |
| blink::rootEditableElement(*m_checkingRange->startContainer())), |
| m_requestData(unrequestedTextCheckingSequence, |
| text, |
| processType, |
| documentMarkersInRange, |
| documentMarkerOffsets), |
| m_requestNumber(requestNumber) { |
| DCHECK(m_checkingRange); |
| DCHECK(m_checkingRange->isConnected()); |
| DCHECK(m_rootEditableElement); |
| } |
| |
| SpellCheckRequest::~SpellCheckRequest() {} |
| |
| DEFINE_TRACE(SpellCheckRequest) { |
| visitor->trace(m_requester); |
| visitor->trace(m_checkingRange); |
| visitor->trace(m_rootEditableElement); |
| TextCheckingRequest::trace(visitor); |
| } |
| |
| void SpellCheckRequest::dispose() { |
| if (m_checkingRange) |
| m_checkingRange->dispose(); |
| } |
| |
| // static |
| SpellCheckRequest* SpellCheckRequest::create( |
| TextCheckingProcessType processType, |
| const EphemeralRange& checkingRange, |
| int requestNumber) { |
| if (checkingRange.isNull()) |
| return nullptr; |
| if (!blink::rootEditableElement( |
| *checkingRange.startPosition().computeContainerNode())) |
| return nullptr; |
| |
| String text = |
| plainText(checkingRange, TextIteratorEmitsObjectReplacementCharacter); |
| if (text.isEmpty()) |
| return nullptr; |
| |
| Range* checkingRangeObject = createRange(checkingRange); |
| |
| const DocumentMarkerVector& markers = |
| checkingRangeObject->ownerDocument().markers().markersInRange( |
| checkingRange, DocumentMarker::SpellCheckClientMarkers()); |
| Vector<uint32_t> hashes(markers.size()); |
| Vector<unsigned> offsets(markers.size()); |
| for (size_t i = 0; i < markers.size(); ++i) { |
| hashes[i] = markers[i]->hash(); |
| offsets[i] = markers[i]->startOffset(); |
| } |
| |
| return new SpellCheckRequest(checkingRangeObject, text, processType, hashes, |
| offsets, requestNumber); |
| } |
| |
| const TextCheckingRequestData& SpellCheckRequest::data() const { |
| return m_requestData; |
| } |
| |
| bool SpellCheckRequest::isValid() const { |
| return m_checkingRange->isConnected() && m_rootEditableElement->isConnected(); |
| } |
| |
| void SpellCheckRequest::didSucceed(const Vector<TextCheckingResult>& results) { |
| if (!m_requester) |
| return; |
| SpellCheckRequester* requester = m_requester; |
| m_requester = nullptr; |
| requester->didCheckSucceed(m_requestData.sequence(), results); |
| } |
| |
| void SpellCheckRequest::didCancel() { |
| if (!m_requester) |
| return; |
| SpellCheckRequester* requester = m_requester; |
| m_requester = nullptr; |
| requester->didCheckCancel(m_requestData.sequence()); |
| } |
| |
| void SpellCheckRequest::setCheckerAndSequence(SpellCheckRequester* requester, |
| int sequence) { |
| DCHECK(!m_requester); |
| DCHECK_EQ(m_requestData.sequence(), unrequestedTextCheckingSequence); |
| m_requester = requester; |
| m_requestData.m_sequence = sequence; |
| } |
| |
| SpellCheckRequester::SpellCheckRequester(LocalFrame& frame) |
| : m_frame(&frame), |
| m_lastRequestSequence(0), |
| m_lastProcessedSequence(0), |
| m_timerToProcessQueuedRequest( |
| this, |
| &SpellCheckRequester::timerFiredToProcessQueuedRequest) {} |
| |
| SpellCheckRequester::~SpellCheckRequester() {} |
| |
| TextCheckerClient& SpellCheckRequester::client() const { |
| return frame().spellChecker().textChecker(); |
| } |
| |
| void SpellCheckRequester::timerFiredToProcessQueuedRequest(TimerBase*) { |
| DCHECK(!m_requestQueue.isEmpty()); |
| if (m_requestQueue.isEmpty()) |
| return; |
| |
| invokeRequest(m_requestQueue.takeFirst()); |
| } |
| |
| void SpellCheckRequester::requestCheckingFor(SpellCheckRequest* request) { |
| if (!request) |
| return; |
| |
| DCHECK_EQ(request->data().sequence(), unrequestedTextCheckingSequence); |
| int sequence = ++m_lastRequestSequence; |
| if (sequence == unrequestedTextCheckingSequence) |
| sequence = ++m_lastRequestSequence; |
| |
| request->setCheckerAndSequence(this, sequence); |
| |
| if (m_timerToProcessQueuedRequest.isActive() || m_processingRequest) { |
| enqueueRequest(request); |
| return; |
| } |
| |
| invokeRequest(request); |
| } |
| |
| void SpellCheckRequester::cancelCheck() { |
| if (m_processingRequest) |
| m_processingRequest->didCancel(); |
| } |
| |
| void SpellCheckRequester::prepareForLeakDetection() { |
| m_timerToProcessQueuedRequest.stop(); |
| // Empty the queue of pending requests to prevent it being a leak source. |
| // Pending spell checker requests are cancellable requests not representing |
| // leaks, just async work items waiting to be processed. |
| // |
| // Rather than somehow wait for this async queue to drain before running |
| // the leak detector, they're all cancelled to prevent flaky leaks being |
| // reported. |
| m_requestQueue.clear(); |
| // WebSpellCheckClient stores a set of WebTextCheckingCompletion objects, |
| // which may store references to already invoked requests. We should clear |
| // these references to prevent them from being a leak source. |
| client().cancelAllPendingRequests(); |
| } |
| |
| void SpellCheckRequester::invokeRequest(SpellCheckRequest* request) { |
| DCHECK(!m_processingRequest); |
| m_processingRequest = request; |
| client().requestCheckingOfString(m_processingRequest); |
| } |
| |
| void SpellCheckRequester::clearProcessingRequest() { |
| if (!m_processingRequest) |
| return; |
| |
| m_processingRequest->dispose(); |
| m_processingRequest.clear(); |
| } |
| |
| void SpellCheckRequester::enqueueRequest(SpellCheckRequest* request) { |
| DCHECK(request); |
| bool continuation = false; |
| if (!m_requestQueue.isEmpty()) { |
| SpellCheckRequest* lastRequest = m_requestQueue.last(); |
| // It's a continuation if the number of the last request got incremented in |
| // the new one and both apply to the same editable. |
| continuation = |
| request->rootEditableElement() == lastRequest->rootEditableElement() && |
| request->requestNumber() == lastRequest->requestNumber() + 1; |
| } |
| |
| // Spellcheck requests for chunks of text in the same element should not |
| // overwrite each other. |
| if (!continuation) { |
| for (auto& requestQueue : m_requestQueue) { |
| if (request->rootEditableElement() != requestQueue->rootEditableElement()) |
| continue; |
| |
| requestQueue = request; |
| return; |
| } |
| } |
| |
| m_requestQueue.append(request); |
| } |
| |
| void SpellCheckRequester::didCheck(int sequence, |
| const Vector<TextCheckingResult>& results) { |
| DCHECK(m_processingRequest); |
| DCHECK_EQ(m_processingRequest->data().sequence(), sequence); |
| if (m_processingRequest->data().sequence() != sequence) { |
| m_requestQueue.clear(); |
| return; |
| } |
| |
| frame().spellChecker().markAndReplaceFor(m_processingRequest, results); |
| |
| if (m_lastProcessedSequence < sequence) |
| m_lastProcessedSequence = sequence; |
| |
| clearProcessingRequest(); |
| if (!m_requestQueue.isEmpty()) |
| m_timerToProcessQueuedRequest.startOneShot(0, BLINK_FROM_HERE); |
| } |
| |
| void SpellCheckRequester::didCheckSucceed( |
| int sequence, |
| const Vector<TextCheckingResult>& results) { |
| // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets |
| // needs to be audited. See http://crbug.com/590369 for more details. |
| frame().document()->updateStyleAndLayoutIgnorePendingStylesheets(); |
| |
| TextCheckingRequestData requestData = m_processingRequest->data(); |
| if (requestData.sequence() == sequence) { |
| DocumentMarker::MarkerTypes markers = |
| DocumentMarker::SpellCheckClientMarkers(); |
| if (m_processingRequest->isValid()) { |
| Range* checkingRange = m_processingRequest->checkingRange(); |
| frame().document()->markers().removeMarkers(EphemeralRange(checkingRange), |
| markers); |
| } |
| } |
| didCheck(sequence, results); |
| } |
| |
| void SpellCheckRequester::didCheckCancel(int sequence) { |
| Vector<TextCheckingResult> results; |
| didCheck(sequence, results); |
| } |
| |
| DEFINE_TRACE(SpellCheckRequester) { |
| visitor->trace(m_frame); |
| visitor->trace(m_processingRequest); |
| visitor->trace(m_requestQueue); |
| } |
| |
| } // namespace blink |