JSValue JSInspectorFrontendHost::search(ExecState* exec, const ArgList& args) { if (args.size() < 2) return jsUndefined(); Node* node = toNode(args.at(0)); if (!node) return jsUndefined(); String target = args.at(1).toString(exec); if (exec->hadException()) return jsUndefined(); MarkedArgumentBuffer result; RefPtr<Range> searchRange(rangeOfContents(node)); ExceptionCode ec = 0; do { RefPtr<Range> resultRange(findPlainText(searchRange.get(), target, true, false)); if (resultRange->collapsed(ec)) break; // A non-collapsed result range can in some funky whitespace cases still not // advance the range's start position (4509328). Break to avoid infinite loop. VisiblePosition newStart = endVisiblePosition(resultRange.get(), DOWNSTREAM); if (newStart == startVisiblePosition(searchRange.get(), DOWNSTREAM)) break; result.append(toJS(exec, resultRange.get())); setStart(searchRange.get(), newStart); } while (true); return constructArray(exec, result); }
static bool relinquishesEditingFocus(Node *node) { ASSERT(node); ASSERT(node->isContentEditable()); Node* root = node->rootEditableElement(); Frame* frame = node->document()->frame(); if (!frame || !root) return false; return frame->editor()->shouldEndEditing(rangeOfContents(root).get()); }
bool isPositionInNode(Node* node, const Position& position) { int offset = 0; Node* domNodeAtPos = DOMContainerNodeForPosition(position); if (domNodeAtPos == position.containerNode()) offset = position.computeOffsetInContainerNode(); RefPtr<Range> rangeForNode = rangeOfContents(node); int ec; return rangeForNode->isPointInRange(domNodeAtPos, offset, ec); }
static JSValueRef search(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* /*exception*/) { InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); if (!controller) return JSValueMakeUndefined(ctx); if (argumentCount < 2 || !JSValueIsString(ctx, arguments[1])) return JSValueMakeUndefined(ctx); Node* node = toNode(toJS(arguments[0])); if (!node) return JSValueMakeUndefined(ctx); JSStringRef string = JSValueToStringCopy(ctx, arguments[1], 0); String target(JSStringGetCharactersPtr(string), JSStringGetLength(string)); JSStringRelease(string); JSObjectRef globalObject = JSContextGetGlobalObject(ctx); JSStringRef constructorString = JSStringCreateWithUTF8CString("Array"); JSObjectRef arrayConstructor = JSValueToObject(ctx, JSObjectGetProperty(ctx, globalObject, constructorString, 0), 0); JSStringRelease(constructorString); JSObjectRef array = JSObjectCallAsConstructor(ctx, arrayConstructor, 0, 0, 0); JSStringRef pushString = JSStringCreateWithUTF8CString("push"); JSValueRef pushValue = JSObjectGetProperty(ctx, array, pushString, 0); JSStringRelease(pushString); JSObjectRef push = JSValueToObject(ctx, pushValue, 0); RefPtr<Range> searchRange(rangeOfContents(node)); int exception = 0; do { RefPtr<Range> resultRange(findPlainText(searchRange.get(), target, true, false)); if (resultRange->collapsed(exception)) break; // A non-collapsed result range can in some funky whitespace cases still not // advance the range's start position (4509328). Break to avoid infinite loop. VisiblePosition newStart = endVisiblePosition(resultRange.get(), DOWNSTREAM); if (newStart == startVisiblePosition(searchRange.get(), DOWNSTREAM)) break; KJS::JSLock lock; JSValueRef arg0 = toRef(toJS(toJS(ctx), resultRange.get())); JSObjectCallAsFunction(ctx, push, array, 1, &arg0, 0); setStart(searchRange.get(), newStart); } while (true); return array; }
WTF::String inputElementText(Element* element) { if (!element) return WTF::String(); WTF::String elementText; if (element->hasTagName(HTMLNames::inputTag)) { const HTMLInputElement* inputElement = static_cast<const HTMLInputElement*>(element); elementText = inputElement->value(); } else if (element->hasTagName(HTMLNames::textareaTag)) { const HTMLTextAreaElement* inputElement = static_cast<const HTMLTextAreaElement*>(element); elementText = inputElement->value(); } else if (element->isContentEditable()) { RefPtr<Range> rangeForNode = rangeOfContents(element); elementText = rangeForNode.get()->text(); } return elementText; }
void InPageSearchManager::scopeStringMatches(const String& text, bool reset, bool locateActiveMatchOnly, Frame* scopingFrame) { if (reset) { if (!locateActiveMatchOnly) { m_activeMatchCount = 0; m_scopingComplete = false; } m_resumeScopingFromRange = 0; m_locatingActiveMatch = true; m_activeMatchIndex = 0; // New search should always start from mainFrame. scopeStringMatchesSoon(m_webPage->mainFrame(), text, false /* reset */, locateActiveMatchOnly); return; } if (m_resumeScopingFromRange && scopingFrame != m_resumeScopingFromRange->ownerDocument().frame()) m_resumeScopingFromRange = 0; RefPtr<Range> searchRange(rangeOfContents(scopingFrame->document())); Node* originalEndContainer = searchRange->endContainer(); int originalEndOffset = searchRange->endOffset(); ExceptionCode ec = 0, ec2 = 0; if (m_resumeScopingFromRange) { searchRange->setStart(m_resumeScopingFromRange->startContainer(), m_resumeScopingFromRange->startOffset(ec2) + 1, ec); if (ec || ec2) { m_scopingComplete = true; // We should stop scoping because of some stale data. return; } } int matchCount = 0; bool timeout = false; double startTime = currentTime(); do { RefPtr<Range> resultRange(findPlainText(searchRange.get(), text, m_scopingCaseInsensitive ? CaseInsensitive : 0)); if (resultRange->collapsed(ec)) { if (!resultRange->startContainer()->isInShadowTree()) break; searchRange->setStartAfter(resultRange->startContainer()->deprecatedShadowAncestorNode(), ec); searchRange->setEnd(originalEndContainer, originalEndOffset, ec); continue; } ++matchCount; bool foundActiveMatch = false; if (m_locatingActiveMatch && areRangesEqual(resultRange.get(), m_activeMatch.get())) { foundActiveMatch = true; m_locatingActiveMatch = false; if (locateActiveMatchOnly) { m_activeMatchIndex += matchCount; m_webPage->m_client->updateFindStringResult(m_activeMatchCount, m_activeMatchIndex); return; } m_activeMatchIndex = m_activeMatchCount + matchCount; } if (!locateActiveMatchOnly && m_highlightAllMatches) resultRange->ownerDocument().markers().addTextMatchMarker(resultRange.get(), foundActiveMatch); searchRange->setStart(resultRange->endContainer(ec), resultRange->endOffset(ec), ec); ShadowRoot* shadowTreeRoot = searchRange->shadowRoot(); if (searchRange->collapsed(ec) && shadowTreeRoot) searchRange->setEnd(shadowTreeRoot, shadowTreeRoot->childNodeCount(), ec); m_resumeScopingFromRange = resultRange; timeout = (currentTime() - startTime) >= MaxScopingDuration; } while (!timeout); if (matchCount > 0) { if (locateActiveMatchOnly) { // We have not found it yet. // m_activeMatchIndex now temporarily remember where we left over in this time slot. m_activeMatchIndex += matchCount; } else { if (m_highlightAllMatches) scopingFrame->editor().setMarkedTextMatchesAreHighlighted(true /* highlight */); m_activeMatchCount += matchCount; m_webPage->m_client->updateFindStringResult(m_activeMatchCount, m_activeMatchIndex); } } if (timeout) scopeStringMatchesSoon(scopingFrame, text, false /* reset */, locateActiveMatchOnly); else { // Scoping is done for this frame. Frame* nextFrame = DOMSupport::incrementFrame(scopingFrame, true /* forward */, false /* wrapFlag */); if (!nextFrame) { m_scopingComplete = true; return; // Scoping is done for all frames; } scopeStringMatchesSoon(nextFrame, text, false /* reset */, locateActiveMatchOnly); } }
void SpellChecker::requestTextChecking(const Element& element) { RefPtr<Range> rangeToCheck = rangeOfContents(const_cast<Element*>(&element)); m_spellCheckRequester->requestCheckingFor(SpellCheckRequest::create(TextCheckingTypeSpelling | TextCheckingTypeGrammar, TextCheckingProcessBatch, rangeToCheck, rangeToCheck)); }
void SpellChecker::advanceToNextMisspelling(bool startBeforeSelection) { // The basic approach is to search in two phases - from the selection end to the end of the doc, and // then we wrap and search from the doc start to (approximately) where we started. // Start at the end of the selection, search to edge of document. Starting at the selection end makes // repeated "check spelling" commands work. VisibleSelection selection(m_frame.selection().selection()); RefPtr<Range> spellingSearchRange(rangeOfContents(m_frame.document())); bool startedWithSelection = false; if (selection.start().deprecatedNode()) { startedWithSelection = true; if (startBeforeSelection) { VisiblePosition start(selection.visibleStart()); // We match AppKit's rule: Start 1 character before the selection. VisiblePosition oneBeforeStart = start.previous(); setStart(spellingSearchRange.get(), oneBeforeStart.isNotNull() ? oneBeforeStart : start); } else { setStart(spellingSearchRange.get(), selection.visibleEnd()); } } Position position = spellingSearchRange->startPosition(); if (!isEditablePosition(position)) { // This shouldn't happen in very often because the Spelling menu items aren't enabled unless the // selection is editable. // This can happen in Mail for a mix of non-editable and editable content (like Stationary), // when spell checking the whole document before sending the message. // In that case the document might not be editable, but there are editable pockets that need to be spell checked. position = firstEditableVisiblePositionAfterPositionInRoot(position, m_frame.document()).deepEquivalent(); if (position.isNull()) return; Position rangeCompliantPosition = position.parentAnchoredEquivalent(); spellingSearchRange->setStart(rangeCompliantPosition.deprecatedNode(), rangeCompliantPosition.deprecatedEditingOffset(), IGNORE_EXCEPTION); startedWithSelection = false; // won't need to wrap } // topNode defines the whole range we want to operate on ContainerNode* topNode = highestEditableRoot(position); // FIXME: lastOffsetForEditing() is wrong here if editingIgnoresContent(highestEditableRoot()) returns true (e.g. a <table>) spellingSearchRange->setEnd(topNode, lastOffsetForEditing(topNode), IGNORE_EXCEPTION); // If spellingSearchRange starts in the middle of a word, advance to the next word so we start checking // at a word boundary. Going back by one char and then forward by a word does the trick. if (startedWithSelection) { VisiblePosition oneBeforeStart = startVisiblePosition(spellingSearchRange.get(), DOWNSTREAM).previous(); if (oneBeforeStart.isNotNull()) setStart(spellingSearchRange.get(), endOfWord(oneBeforeStart)); // else we were already at the start of the editable node } if (spellingSearchRange->collapsed()) return; // nothing to search in // We go to the end of our first range instead of the start of it, just to be sure // we don't get foiled by any word boundary problems at the start. It means we might // do a tiny bit more searching. Node* searchEndNodeAfterWrap = spellingSearchRange->endContainer(); int searchEndOffsetAfterWrap = spellingSearchRange->endOffset(); int misspellingOffset = 0; GrammarDetail grammarDetail; int grammarPhraseOffset = 0; RefPtr<Range> grammarSearchRange = nullptr; String badGrammarPhrase; String misspelledWord; bool isSpelling = true; int foundOffset = 0; String foundItem; RefPtr<Range> firstMisspellingRange = nullptr; if (unifiedTextCheckerEnabled()) { grammarSearchRange = spellingSearchRange->cloneRange(); foundItem = TextCheckingHelper(spellCheckerClient(), spellingSearchRange).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail); if (isSpelling) { misspelledWord = foundItem; misspellingOffset = foundOffset; } else { badGrammarPhrase = foundItem; grammarPhraseOffset = foundOffset; } } else { misspelledWord = TextCheckingHelper(spellCheckerClient(), spellingSearchRange).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange); grammarSearchRange = spellingSearchRange->cloneRange(); if (!misspelledWord.isEmpty()) { // Stop looking at start of next misspelled word CharacterIterator chars(grammarSearchRange.get()); chars.advance(misspellingOffset); grammarSearchRange->setEnd(chars.range()->startContainer(), chars.range()->startOffset(), IGNORE_EXCEPTION); } if (isGrammarCheckingEnabled()) badGrammarPhrase = TextCheckingHelper(spellCheckerClient(), grammarSearchRange).findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false); } // If we found neither bad grammar nor a misspelled word, wrap and try again (but don't bother if we started at the beginning of the // block rather than at a selection). if (startedWithSelection && !misspelledWord && !badGrammarPhrase) { spellingSearchRange->setStart(topNode, 0, IGNORE_EXCEPTION); // going until the end of the very first chunk we tested is far enough spellingSearchRange->setEnd(searchEndNodeAfterWrap, searchEndOffsetAfterWrap, IGNORE_EXCEPTION); if (unifiedTextCheckerEnabled()) { grammarSearchRange = spellingSearchRange->cloneRange(); foundItem = TextCheckingHelper(spellCheckerClient(), spellingSearchRange).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail); if (isSpelling) { misspelledWord = foundItem; misspellingOffset = foundOffset; } else { badGrammarPhrase = foundItem; grammarPhraseOffset = foundOffset; } } else { misspelledWord = TextCheckingHelper(spellCheckerClient(), spellingSearchRange).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange); grammarSearchRange = spellingSearchRange->cloneRange(); if (!misspelledWord.isEmpty()) { // Stop looking at start of next misspelled word CharacterIterator chars(grammarSearchRange.get()); chars.advance(misspellingOffset); grammarSearchRange->setEnd(chars.range()->startContainer(), chars.range()->startOffset(), IGNORE_EXCEPTION); } if (isGrammarCheckingEnabled()) badGrammarPhrase = TextCheckingHelper(spellCheckerClient(), grammarSearchRange).findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false); } } if (!badGrammarPhrase.isEmpty()) { // We found bad grammar. Since we only searched for bad grammar up to the first misspelled word, the bad grammar // takes precedence and we ignore any potential misspelled word. Select the grammar detail, update the spelling // panel, and store a marker so we draw the green squiggle later. ASSERT(badGrammarPhrase.length() > 0); ASSERT(grammarDetail.location != -1 && grammarDetail.length > 0); // FIXME 4859190: This gets confused with doubled punctuation at the end of a paragraph RefPtr<Range> badGrammarRange = TextIterator::subrange(grammarSearchRange.get(), grammarPhraseOffset + grammarDetail.location, grammarDetail.length); m_frame.selection().setSelection(VisibleSelection(badGrammarRange.get(), SEL_DEFAULT_AFFINITY)); m_frame.selection().revealSelection(); m_frame.document()->markers().addMarker(badGrammarRange.get(), DocumentMarker::Grammar, grammarDetail.userDescription); } else if (!misspelledWord.isEmpty()) { // We found a misspelling, but not any earlier bad grammar. Select the misspelling, update the spelling panel, and store // a marker so we draw the red squiggle later. RefPtr<Range> misspellingRange = TextIterator::subrange(spellingSearchRange.get(), misspellingOffset, misspelledWord.length()); m_frame.selection().setSelection(VisibleSelection(misspellingRange.get(), DOWNSTREAM)); m_frame.selection().revealSelection(); spellCheckerClient().updateSpellingUIWithMisspelledWord(misspelledWord); m_frame.document()->markers().addMarker(misspellingRange.get(), DocumentMarker::Spelling); } }
void InPageSearchManager::scopeStringMatches(const String& text, bool reset, Frame* scopingFrame) { if (reset) { m_activeMatchCount = 0; m_resumeScopingFromRange = 0; m_scopingComplete = false; m_locatingActiveMatch = true; // New search should always start from mainFrame. scopeStringMatchesSoon(m_webPage->mainFrame(), text, false /* reset */); return; } if (m_resumeScopingFromRange && scopingFrame != m_resumeScopingFromRange->ownerDocument()->frame()) m_resumeScopingFromRange = 0; RefPtr<Range> searchRange(rangeOfContents(scopingFrame->document())); Node* originalEndContainer = searchRange->endContainer(); int originalEndOffset = searchRange->endOffset(); ExceptionCode ec = 0, ec2 = 0; if (m_resumeScopingFromRange) { searchRange->setStart(m_resumeScopingFromRange->startContainer(), m_resumeScopingFromRange->startOffset(ec2) + 1, ec); if (ec || ec2) { m_scopingComplete = true; // We should stop scoping because of some stale data. return; } } int matchCount = 0; bool timeout = false; double startTime = currentTime(); do { RefPtr<Range> resultRange(findPlainText(searchRange.get(), text, CaseInsensitive)); if (resultRange->collapsed(ec)) { if (!resultRange->startContainer()->isInShadowTree()) break; searchRange->setStartAfter(resultRange->startContainer()->shadowAncestorNode(), ec); searchRange->setEnd(originalEndContainer, originalEndOffset, ec); continue; } if (scopingFrame->editor()->insideVisibleArea(resultRange.get())) { ++matchCount; bool foundActiveMatch = false; if (m_locatingActiveMatch && areRangesEqual(resultRange.get(), m_activeMatch.get())) { foundActiveMatch = true; m_locatingActiveMatch = false; m_activeMatchIndex = m_activeMatchCount + matchCount; // FIXME: We need to notify client with m_activeMatchIndex. } resultRange->ownerDocument()->markers()->addTextMatchMarker(resultRange.get(), foundActiveMatch); } searchRange->setStart(resultRange->endContainer(ec), resultRange->endOffset(ec), ec); Node* shadowTreeRoot = searchRange->shadowTreeRootNode(); if (searchRange->collapsed(ec) && shadowTreeRoot) searchRange->setEnd(shadowTreeRoot, shadowTreeRoot->childNodeCount(), ec); m_resumeScopingFromRange = resultRange; timeout = (currentTime() - startTime) >= MaxScopingDuration; } while (!timeout); if (matchCount > 0) { scopingFrame->editor()->setMarkedTextMatchesAreHighlighted(true /* highlight */); m_activeMatchCount += matchCount; } if (timeout) scopeStringMatchesSoon(scopingFrame, text, false /* reset */); else { // Scoping is done for this frame. Frame* nextFrame = DOMSupport::incrementFrame(scopingFrame, true /* forward */, false /* wrapFlag */); if (!nextFrame) { m_scopingComplete = true; return; // Scoping is done for all frames; } scopeStringMatchesSoon(nextFrame, text, false /* reset */); } }
PassRefPtrWillBeRawPtr<Range> PlainTextRange::createRangeFor(const ContainerNode& scope, GetRangeFor getRangeFor) const { ASSERT(isNotNull()); RefPtrWillBeRawPtr<Range> resultRange = scope.document().createRange(); size_t docTextPosition = 0; bool startRangeFound = false; Position textRunStartPosition; Position textRunEndPosition; TextIteratorBehaviorFlags behaviorFlags = TextIteratorEmitsObjectReplacementCharacter; if (getRangeFor == ForSelection) behaviorFlags |= TextIteratorEmitsCharactersBetweenAllVisiblePositions; auto range = rangeOfContents(const_cast<ContainerNode*>(&scope)); TextIterator it(range->startPosition(), range->endPosition(), behaviorFlags); // FIXME: the atEnd() check shouldn't be necessary, workaround for <http://bugs.webkit.org/show_bug.cgi?id=6289>. if (!start() && !length() && it.atEnd()) { resultRange->setStart(it.currentContainer(), 0, ASSERT_NO_EXCEPTION); resultRange->setEnd(it.currentContainer(), 0, ASSERT_NO_EXCEPTION); return resultRange.release(); } for (; !it.atEnd(); it.advance()) { int len = it.length(); textRunStartPosition = it.startPositionInCurrentContainer(); textRunEndPosition = it.endPositionInCurrentContainer(); bool foundStart = start() >= docTextPosition && start() <= docTextPosition + len; bool foundEnd = end() >= docTextPosition && end() <= docTextPosition + len; // Fix textRunRange->endPosition(), but only if foundStart || foundEnd, because it is only // in those cases that textRunRange is used. if (foundEnd) { // FIXME: This is a workaround for the fact that the end of a run // is often at the wrong position for emitted '\n's or if the // renderer of the current node is a replaced element. if (len == 1 && (it.characterAt(0) == '\n' || it.isInsideReplacedElement())) { it.advance(); if (!it.atEnd()) { textRunEndPosition = it.startPositionInCurrentContainer(); } else { Position runEnd = VisiblePosition(textRunStartPosition).next().deepEquivalent(); if (runEnd.isNotNull()) textRunEndPosition = createLegacyEditingPosition(runEnd.containerNode(), runEnd.computeOffsetInContainerNode()); } } } if (foundStart) { startRangeFound = true; if (textRunStartPosition.containerNode()->isTextNode()) { int offset = start() - docTextPosition; resultRange->setStart(textRunStartPosition.containerNode(), offset + textRunStartPosition.offsetInContainerNode(), IGNORE_EXCEPTION); } else { if (start() == docTextPosition) resultRange->setStart(textRunStartPosition.containerNode(), textRunStartPosition.offsetInContainerNode(), IGNORE_EXCEPTION); else resultRange->setStart(textRunEndPosition.containerNode(), textRunEndPosition.offsetInContainerNode(), IGNORE_EXCEPTION); } } if (foundEnd) { if (textRunStartPosition.containerNode()->isTextNode()) { int offset = end() - docTextPosition; resultRange->setEnd(textRunStartPosition.containerNode(), offset + textRunStartPosition.offsetInContainerNode(), IGNORE_EXCEPTION); } else { if (end() == docTextPosition) resultRange->setEnd(textRunStartPosition.containerNode(), textRunStartPosition.offsetInContainerNode(), IGNORE_EXCEPTION); else resultRange->setEnd(textRunEndPosition.containerNode(), textRunEndPosition.offsetInContainerNode(), IGNORE_EXCEPTION); } docTextPosition += len; break; } docTextPosition += len; } if (!startRangeFound) return nullptr; if (length() && end() > docTextPosition) { // end() is out of bounds resultRange->setEnd(textRunEndPosition.containerNode(), textRunEndPosition.offsetInContainerNode(), IGNORE_EXCEPTION); } return resultRange.release(); }