void InputMethodController::setCompositionFromExistingText(const Vector<CompositionUnderline>& underlines, unsigned compositionStart, unsigned compositionEnd) { Element* editable = frame().selection().rootEditableElement(); if (!editable) return; const EphemeralRange range = PlainTextRange(compositionStart, compositionEnd).createRange(*editable); if (range.isNull()) return; const Position start = range.startPosition(); if (editableRootForPosition(start) != editable) return; const Position end = range.endPosition(); if (editableRootForPosition(end) != editable) return; clear(); for (const auto& underline : underlines) { unsigned underlineStart = compositionStart + underline.startOffset; unsigned underlineEnd = compositionStart + underline.endOffset; EphemeralRange ephemeralLineRange = PlainTextRange(underlineStart, underlineEnd).createRange(*editable); if (ephemeralLineRange.isNull()) continue; frame().document()->markers().addCompositionMarker(ephemeralLineRange.startPosition(), ephemeralLineRange.endPosition(), underline.color, underline.thick, underline.backgroundColor); } m_hasComposition = true; if (!m_compositionRange) m_compositionRange = Range::create(range.document()); m_compositionRange->setStart(range.startPosition()); m_compositionRange->setEnd(range.endPosition()); }
void SurroundingText::initialize(const Position& startPosition, const Position& endPosition, unsigned maxLength) { ASSERT(startPosition.document() == endPosition.document()); const unsigned halfMaxLength = maxLength / 2; Document* document = startPosition.document(); // The position will have no document if it is null (as in no position). if (!document || !document->documentElement()) return; // The forward range starts at the selection end and ends at the document's // end. It will then be updated to only contain the text in the text in the // right range around the selection. CharacterIterator forwardIterator(endPosition, lastPositionInNode(document->documentElement()).parentAnchoredEquivalent(), TextIteratorStopsOnFormControls); // FIXME: why do we stop going trough the text if we were not able to select something on the right? if (!forwardIterator.atEnd()) forwardIterator.advance(maxLength - halfMaxLength); EphemeralRange forwardRange = forwardIterator.range(); if (forwardRange.isNull() || !Range::create(*document, endPosition, forwardRange.startPosition())->text().length()) return; // Same as with the forward range but with the backward range. The range // starts at the document's start and ends at the selection start and will // be updated. BackwardsCharacterIterator backwardsIterator(firstPositionInNode(document->documentElement()).parentAnchoredEquivalent(), startPosition, TextIteratorStopsOnFormControls); if (!backwardsIterator.atEnd()) backwardsIterator.advance(halfMaxLength); m_startOffsetInContent = Range::create(*document, backwardsIterator.endPosition(), startPosition)->text().length(); m_endOffsetInContent = Range::create(*document, backwardsIterator.endPosition(), endPosition)->text().length(); m_contentRange = Range::create(*document, backwardsIterator.endPosition(), forwardRange.startPosition()); ASSERT(m_contentRange); }
// static PassRefPtrWillBeRawPtr<SpellCheckRequest> SpellCheckRequest::create(TextCheckingTypeMask textCheckingOptions, TextCheckingProcessType processType, const EphemeralRange& checkingRange, const EphemeralRange& paragraphRange, int requestNumber) { if (checkingRange.isNull()) return nullptr; if (!checkingRange.startPosition().computeContainerNode()->rootEditableElement()) return nullptr; String text = plainText(checkingRange, TextIteratorEmitsObjectReplacementCharacter); if (text.isEmpty()) return nullptr; RefPtrWillBeRawPtr<Range> checkingRangeObject = createRange(checkingRange); RefPtrWillBeRawPtr<Range> paragraphRangeObject = nullptr; // Share identical Range objects. if (checkingRange == paragraphRange) paragraphRangeObject = checkingRangeObject; else paragraphRangeObject = createRange(paragraphRange); 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 adoptRefWillBeNoop(new SpellCheckRequest(checkingRangeObject, paragraphRangeObject, text, textCheckingOptions, processType, hashes, offsets, requestNumber)); }
PlainTextRange InputMethodController::getSelectionOffsets() const { EphemeralRange range = firstEphemeralRangeOf(frame().selection().selection()); if (range.isNull()) return PlainTextRange(); ContainerNode* editable = frame().selection().rootEditableElementOrTreeScopeRootNode(); ASSERT(editable); return PlainTextRange::create(*editable, range); }
// static WebRange WebRange::fromDocumentRange(WebLocalFrame* frame, int start, int length) { LocalFrame* webFrame = toWebLocalFrameImpl(frame)->frame(); Element* selectionRoot = webFrame->selection().rootEditableElement(); ContainerNode* scope = selectionRoot ? selectionRoot : webFrame->document()->documentElement(); const EphemeralRange range = PlainTextRange(start, start + length).createRange(*scope); if (range.isNull()) return WebRange(); return Range::create(range.document(), range.startPosition(), range.endPosition()); }
void InputMethodController::selectComposition() const { const EphemeralRange range = compositionEphemeralRange(); if (range.isNull()) return; // The composition can start inside a composed character sequence, so we have to override checks. // See <http://bugs.webkit.org/show_bug.cgi?id=15781> VisibleSelection selection; selection.setWithoutValidation(range.startPosition(), range.endPosition()); frame().selection().setSelection(selection, 0); }
bool InputMethodController::setSelectionOffsets(const PlainTextRange& selectionOffsets) { if (selectionOffsets.isNull()) return false; Element* rootEditableElement = frame().selection().rootEditableElement(); if (!rootEditableElement) return false; const EphemeralRange range = selectionOffsets.createRange(*rootEditableElement); if (range.isNull()) return false; return frame().selection().setSelectedRange(range, VP_DEFAULT_AFFINITY, SelectionDirectionalMode::NonDirectional, FrameSelection::CloseTyping); }
bool SelectionEditor::setSelectedRange(const EphemeralRange& range, TextAffinity affinity, SelectionDirectionalMode directional, FrameSelection::SetSelectionOptions options) { if (range.isNull()) return false; // Non-collapsed ranges are not allowed to start at the end of a line that is wrapped, // they start at the beginning of the next line instead m_logicalRange = nullptr; stopObservingVisibleSelectionChangeIfNecessary(); // Since |FrameSeleciton::setSelection()| dispatches events and DOM tree // can be modified by event handlers, we should create |Range| object before // calling it. m_logicalRange = createRange(range); VisibleSelection newSelection(range.startPosition(), range.endPosition(), affinity, directional == SelectionDirectionalMode::Directional); m_frameSelection->setSelection(newSelection, options); startObservingVisibleSelectionChange(); return true; }
PlainTextRange PlainTextRange::create(const ContainerNode& scope, const EphemeralRange& range) { if (range.isNull()) return PlainTextRange(); // The critical assumption is that this only gets called with ranges that // concentrate on a given area containing the selection root. This is done // because of text fields and textareas. The DOM for those is not // directly in the document DOM, so ensure that the range does not cross a // boundary of one of those. Node* startContainer = range.startPosition().computeContainerNode(); if (startContainer != &scope && !startContainer->isDescendantOf(&scope)) return PlainTextRange(); Node* endContainer = range.endPosition().computeContainerNode(); if (endContainer != scope && !endContainer->isDescendantOf(&scope)) return PlainTextRange(); size_t start = TextIterator::rangeLength(Position(&const_cast<ContainerNode&>(scope), 0), range.startPosition()); size_t end = TextIterator::rangeLength(Position(&const_cast<ContainerNode&>(scope), 0), range.endPosition()); return PlainTextRange(start, end); }
void InputMethodController::setComposition(const String& text, const Vector<CompositionUnderline>& underlines, unsigned selectionStart, unsigned selectionEnd) { Editor::RevealSelectionScope revealSelectionScope(&editor()); // Updates styles before setting selection for composition to prevent // inserting the previous composition text into text nodes oddly. // See https://bugs.webkit.org/show_bug.cgi?id=46868 frame().document()->updateLayoutTreeIfNeeded(); selectComposition(); if (frame().selection().isNone()) return; if (Element* target = frame().document()->focusedElement()) { // Dispatch an appropriate composition event to the focused node. // We check the composition status and choose an appropriate composition event since this // function is used for three purposes: // 1. Starting a new composition. // Send a compositionstart and a compositionupdate event when this function creates // a new composition node, i.e. // !hasComposition() && !text.isEmpty(). // Sending a compositionupdate event at this time ensures that at least one // compositionupdate event is dispatched. // 2. Updating the existing composition node. // Send a compositionupdate event when this function updates the existing composition // node, i.e. hasComposition() && !text.isEmpty(). // 3. Canceling the ongoing composition. // Send a compositionend event when function deletes the existing composition node, i.e. // !hasComposition() && test.isEmpty(). RefPtrWillBeRawPtr<CompositionEvent> event = nullptr; if (!hasComposition()) { // We should send a compositionstart event only when the given text is not empty because this // function doesn't create a composition node when the text is empty. if (!text.isEmpty()) { target->dispatchEvent(CompositionEvent::create(EventTypeNames::compositionstart, frame().domWindow(), frame().selectedText())); event = CompositionEvent::create(EventTypeNames::compositionupdate, frame().domWindow(), text); } } else { if (!text.isEmpty()) event = CompositionEvent::create(EventTypeNames::compositionupdate, frame().domWindow(), text); else event = CompositionEvent::create(EventTypeNames::compositionend, frame().domWindow(), text); } if (event.get()) target->dispatchEvent(event); } // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input // will delete the old composition with an optimized replace operation. if (text.isEmpty()) { ASSERT(frame().document()); TypingCommand::deleteSelection(*frame().document(), TypingCommand::PreventSpellChecking); } clear(); if (text.isEmpty()) return; ASSERT(frame().document()); TypingCommand::insertText(*frame().document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionUpdate); // Find out what node has the composition now. Position base = mostForwardCaretPosition(frame().selection().base()); Node* baseNode = base.anchorNode(); if (!baseNode || !baseNode->isTextNode()) return; Position extent = frame().selection().extent(); Node* extentNode = extent.anchorNode(); if (baseNode != extentNode) return; unsigned extentOffset = extent.computeOffsetInContainerNode(); unsigned baseOffset = base.computeOffsetInContainerNode(); if (baseOffset + text.length() != extentOffset) return; m_isDirty = true; m_hasComposition = true; if (!m_compositionRange) m_compositionRange = Range::create(baseNode->document()); m_compositionRange->setStart(baseNode, baseOffset); m_compositionRange->setEnd(baseNode, extentOffset); if (baseNode->layoutObject()) baseNode->layoutObject()->setShouldDoFullPaintInvalidation(); unsigned start = std::min(baseOffset + selectionStart, extentOffset); unsigned end = std::min(std::max(start, baseOffset + selectionEnd), extentOffset); RefPtrWillBeRawPtr<Range> selectedRange = Range::create(baseNode->document(), baseNode, start, baseNode, end); frame().selection().setSelectedRange(selectedRange.get(), TextAffinity::Downstream, SelectionDirectionalMode::NonDirectional, NotUserTriggered); if (underlines.isEmpty()) { frame().document()->markers().addCompositionMarker(m_compositionRange->startPosition(), m_compositionRange->endPosition(), Color::black, false, LayoutTheme::theme().platformDefaultCompositionBackgroundColor()); return; } for (const auto& underline : underlines) { unsigned underlineStart = baseOffset + underline.startOffset; unsigned underlineEnd = baseOffset + underline.endOffset; EphemeralRange ephemeralLineRange = EphemeralRange(Position(baseNode, underlineStart), Position(baseNode, underlineEnd)); if (ephemeralLineRange.isNull()) continue; frame().document()->markers().addCompositionMarker(ephemeralLineRange.startPosition(), ephemeralLineRange.endPosition(), underline.color, underline.thick, underline.backgroundColor); } }