static void adjustPositionForUserSelectAll(VisiblePosition& pos, bool isForward) { if (Node* rootUserSelectAll = EditingStrategy::rootUserSelectAllForNode( pos.deepEquivalent().anchorNode())) pos = createVisiblePosition( isForward ? mostForwardCaretPosition(Position::afterNode(rootUserSelectAll), CanCrossEditingBoundary) : mostBackwardCaretPosition(Position::beforeNode(rootUserSelectAll), CanCrossEditingBoundary)); }
void PendingSelection::commitAlgorithm(LayoutView& layoutView) { if (!hasPendingSelection()) return; ASSERT(!layoutView.needsLayout()); m_hasPendingSelection = false; const VisibleSelectionTemplate<Strategy> originalSelection = m_frameSelection->visibleSelection<Strategy>(); // Skip if pending VisibilePositions became invalid before we reach here. if (!isSelectionInDocument(originalSelection, layoutView.document())) return; // Construct a new VisibleSolution, since visibleSelection() is not necessarily // valid, and the following steps assume a valid selection. // See <https://bugs.webkit.org/show_bug.cgi?id=69563> and // <rdar://problem/10232866>. const VisibleSelectionTemplate<Strategy> selection = calcVisibleSelectionAlgorithm<Strategy>(originalSelection); if (!selection.isRange()) { layoutView.clearSelection(); return; } // Use the rightmost candidate for the start of the selection, and the // leftmost candidate for the end of the selection. Example: foo <a>bar</a>. // Imagine that a line wrap occurs after 'foo', and that 'bar' is selected. // If we pass [foo, 3] as the start of the selection, the selection painting // code will think that content on the line containing 'foo' is selected // and will fill the gap before 'bar'. PositionTemplate<Strategy> startPos = selection.start(); PositionTemplate<Strategy> candidate = mostForwardCaretPosition(startPos); if (isVisuallyEquivalentCandidate(candidate)) startPos = candidate; PositionTemplate<Strategy> endPos = selection.end(); candidate = mostBackwardCaretPosition(endPos); if (isVisuallyEquivalentCandidate(candidate)) endPos = candidate; // We can get into a state where the selection endpoints map to the same // |VisiblePosition| when a selection is deleted because we don't yet notify // the |FrameSelection| of text removal. if (startPos.isNull() || endPos.isNull() || selection.visibleStart().deepEquivalent() == selection.visibleEnd().deepEquivalent()) return; LayoutObject* startLayoutObject = startPos.anchorNode()->layoutObject(); LayoutObject* endLayoutObject = endPos.anchorNode()->layoutObject(); if (!startLayoutObject || !endLayoutObject) return; ASSERT(layoutView == startLayoutObject->view() && layoutView == endLayoutObject->view()); layoutView.setSelection(startLayoutObject, startPos.computeEditingOffset(), endLayoutObject, endPos.computeEditingOffset()); }
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); } }