static void resolveShadowTree(ShadowRoot* shadowRoot, RenderStyle* parentElementStyle, Style::Change change) { if (!shadowRoot) return; StyleResolver& styleResolver = shadowRoot->document()->ensureStyleResolver(); styleResolver.pushParentShadowRoot(shadowRoot); for (Node* child = shadowRoot->firstChild(); child; child = child->nextSibling()) { if (child->isTextNode()) { // Current user agent ShadowRoots don't have immediate text children so this branch is never actually taken. updateTextStyle(toText(child), parentElementStyle, change); continue; } resolveTree(toElement(child), change); } styleResolver.popParentShadowRoot(shadowRoot); shadowRoot->clearNeedsStyleRecalc(); shadowRoot->clearChildNeedsStyleRecalc(); }
static inline void executeInsertTextTask(HTMLConstructionSiteTask& task) { ASSERT(task.operation == HTMLConstructionSiteTask::InsertText); ASSERT(task.child->isTextNode()); // Merge text nodes into previous ones if possible: // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#insert-a-character Text* newText = toText(task.child.get()); Node* previousChild = task.parent->lastChild(); if (previousChild && previousChild->isTextNode()) { Text* previousText = toText(previousChild); unsigned lengthLimit = textLengthLimitForContainer(*task.parent); if (previousText->length() + newText->length() < lengthLimit) { previousText->parserAppendData(newText->data()); return; } } insert(task); }
static Position positionForIndex(TextControlInnerTextElement* innerText, unsigned index) { unsigned remainingCharactersToMoveForward = index; Node* lastBrOrText = innerText; for (Node* node = innerText; node; node = NodeTraversal::next(node, innerText)) { if (node->hasTagName(brTag)) { if (!remainingCharactersToMoveForward) return positionBeforeNode(node); remainingCharactersToMoveForward--; lastBrOrText = node; } else if (node->isTextNode()) { Text& text = toText(*node); if (remainingCharactersToMoveForward < text.length()) return Position(&text, remainingCharactersToMoveForward); remainingCharactersToMoveForward -= text.length(); lastBrOrText = node; } } return lastPositionInOrAfterNode(lastBrOrText); }
void AndroidHitTestResult::searchContentDetectors() { AddressDetector address; PhoneEmailDetector phoneEmail; Node* node = m_hitTestResult.innerNode(); if (!node || !node->isTextNode()) return; if (!m_hitTestResult.absoluteLinkURL().isEmpty()) return; WebKit::WebHitTestInfo webHitTest(m_hitTestResult); m_searchResult = address.FindTappedContent(webHitTest); if (!m_searchResult.valid) { m_searchResult = phoneEmail.FindTappedContent(webHitTest); } if (m_searchResult.valid) { m_highlightRects.clear(); RefPtr<Range> range = (PassRefPtr<Range>) m_searchResult.range; range->textRects(m_highlightRects, true); } }
UChar32 VisiblePosition::characterAfter() const { // We canonicalize to the first of two equivalent candidates, but the second of the two candidates // is the one that will be inside the text node containing the character after this visible position. Position pos = m_deepPosition.downstream(); Node* node = pos.containerNode(); if (!node || !node->isTextNode() || pos.anchorType() == Position::PositionIsAfterAnchor) return 0; ASSERT(pos.anchorType() == Position::PositionIsBeforeAnchor || pos.anchorType() == Position::PositionIsOffsetInAnchor); Text* textNode = static_cast<Text*>(pos.containerNode()); unsigned offset = pos.anchorType() == Position::PositionIsOffsetInAnchor ? pos.offsetInContainerNode() : 0; unsigned length = textNode->length(); if (offset >= length) return 0; UChar32 ch; const UChar* characters = textNode->data().characters(); U16_NEXT(characters, offset, length, ch); return ch; }
static CSSValueID determineTextDirection(DocumentFragment* vttRoot) { DEFINE_STATIC_LOCAL(const String, rtTag, ("rt")); ASSERT(vttRoot); // Apply the Unicode Bidirectional Algorithm's Paragraph Level steps to the // concatenation of the values of each WebVTT Text Object in nodes, in a // pre-order, depth-first traversal, excluding WebVTT Ruby Text Objects and // their descendants. TextDirection textDirection = LTR; for (Node* node = vttRoot->firstChild(); node; node = NodeTraversal::next(*node, vttRoot)) { if (!node->isTextNode() || node->localName() == rtTag) continue; bool hasStrongDirectionality; textDirection = determineDirectionality(node->nodeValue(), hasStrongDirectionality); if (hasStrongDirectionality) break; } return isLeftToRightDirection(textDirection) ? CSSValueLtr : CSSValueRtl; }
void TextTrackCue::determineTextDirection() { DEFINE_STATIC_LOCAL(const String, rtTag, (ASCIILiteral("rt"))); createWebVTTNodeTree(); // Apply the Unicode Bidirectional Algorithm's Paragraph Level steps to the // concatenation of the values of each WebVTT Text Object in nodes, in a // pre-order, depth-first traversal, excluding WebVTT Ruby Text Objects and // their descendants. StringBuilder paragraphBuilder; for (Node* node = m_webVTTNodeTree->firstChild(); node; node = NodeTraversal::next(node, m_webVTTNodeTree.get())) { if (!node->isTextNode() || node->localName() == rtTag) continue; paragraphBuilder.append(node->nodeValue()); } String paragraph = paragraphBuilder.toString(); if (!paragraph.length()) return; for (size_t i = 0; i < paragraph.length(); ++i) { UChar current = paragraph[i]; if (!current || isCueParagraphSeparator(current)) return; if (UChar current = paragraph[i]) { WTF::Unicode::Direction charDirection = WTF::Unicode::direction(current); if (charDirection == WTF::Unicode::LeftToRight) { m_displayDirection = CSSValueLtr; return; } if (charDirection == WTF::Unicode::RightToLeft || charDirection == WTF::Unicode::RightToLeftArabic) { m_displayDirection = CSSValueRtl; return; } } } }
void HTMLConstructionSite::insertTextNode(const String& characters, WhitespaceMode whitespaceMode) { HTMLConstructionSiteTask task; task.parent = currentNode(); if (shouldFosterParent()) findFosterSite(task); // Strings composed entirely of whitespace are likely to be repeated. // Turn them into AtomicString so we share a single string for each. bool shouldUseAtomicString = whitespaceMode == AllWhitespace || (whitespaceMode == WhitespaceUnknown && isAllWhitespace(characters)); unsigned currentPosition = 0; // FIXME: Splitting text nodes into smaller chunks contradicts HTML5 spec, but is currently necessary // for performance, see <https://bugs.webkit.org/show_bug.cgi?id=55898>. Node* previousChild = task.nextChild ? task.nextChild->previousSibling() : task.parent->lastChild(); if (previousChild && previousChild->isTextNode()) { // FIXME: We're only supposed to append to this text node if it // was the last text node inserted by the parser. CharacterData* textNode = static_cast<CharacterData*>(previousChild); currentPosition = textNode->parserAppendData(characters, 0, Text::defaultLengthLimit); } while (currentPosition < characters.length()) { RefPtr<Text> textNode = Text::createWithLengthLimit(task.parent->document(), shouldUseAtomicString ? AtomicString(characters).string() : characters, currentPosition); // If we have a whole string of unbreakable characters the above could lead to an infinite loop. Exceeding the length limit is the lesser evil. if (!textNode->length()) { String substring = characters.substring(currentPosition); textNode = Text::create(task.parent->document(), shouldUseAtomicString ? AtomicString(substring).string() : substring); } currentPosition += textNode->length(); ASSERT(currentPosition <= characters.length()); task.child = textNode.release(); executeTask(task); } }
void HTMLTextAreaElement::setDefaultValue(const String& defaultValue) { // To preserve comments, remove only the text nodes, then add a single text // node. HeapVector<Member<Node>> textNodes; for (Node* n = firstChild(); n; n = n->nextSibling()) { if (n->isTextNode()) textNodes.append(n); } for (const auto& text : textNodes) removeChild(text.get(), IGNORE_EXCEPTION); // Normalize line endings. String value = defaultValue; value.replace("\r\n", "\n"); value.replace('\r', '\n'); insertBefore(document().createTextNode(value), firstChild(), IGNORE_EXCEPTION); if (!m_isDirty) setNonDirtyValue(value); }
static void attachShadowRoot(ShadowRoot* shadowRoot, const AttachContext& context) { if (shadowRoot->attached()) return; StyleResolver& styleResolver = shadowRoot->document()->ensureStyleResolver(); styleResolver.pushParentShadowRoot(shadowRoot); Style::AttachContext childrenContext(context); childrenContext.resolvedStyle = 0; for (Node* child = shadowRoot->firstChild(); child; child = child->nextSibling()) { if (child->isTextNode()) { toText(child)->attachText(); continue; } if (child->isElementNode()) attachRenderTree(toElement(child), childrenContext); } styleResolver.popParentShadowRoot(shadowRoot); shadowRoot->clearNeedsStyleRecalc(); shadowRoot->setAttached(true); }
void Attr::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta) { if (m_ignoreChildrenChanged > 0) return; Node::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta); // FIXME: We should include entity references in the value String val = ""; for (Node *n = firstChild(); n; n = n->nextSibling()) { if (n->isTextNode()) val += static_cast<Text *>(n)->data(); } if (m_element && m_element->isIdAttributeName(m_attribute->name())) m_element->updateId(m_attribute->value(), val); m_attribute->setValue(val.impl()); if (m_element) m_element->attributeChanged(m_attribute.get()); }
unsigned HTMLTextFormControlElement::indexForPosition(const Position& passedPosition) const { TextControlInnerTextElement* innerText = innerTextElement(); if (!innerText || !innerText->contains(passedPosition.anchorNode()) || passedPosition.isNull()) return 0; if (positionBeforeNode(innerText) == passedPosition) return 0; unsigned index = 0; Node* startNode = passedPosition.computeNodeBeforePosition(); if (!startNode) startNode = passedPosition.containerNode(); ASSERT(startNode); ASSERT(innerText->contains(startNode)); for (Node* node = startNode; node; node = NodeTraversal::previous(node, innerText)) { if (node->isTextNode()) { unsigned length = toText(*node).length(); if (node == passedPosition.containerNode()) index += std::min<unsigned>(length, passedPosition.offsetInContainerNode()); else index += length; } else if (node->hasTagName(brTag)) index++; } unsigned length = innerTextValue().length(); index = std::min(index, length); // FIXME: We shouldn't have to call innerTextValue() just to ignore the last LF. See finishText. #ifndef ASSERT_DISABLED VisiblePosition visiblePosition = passedPosition; unsigned indexComputedByVisiblePosition = 0; if (visiblePosition.isNotNull()) indexComputedByVisiblePosition = WebCore::indexForVisiblePosition(innerText, visiblePosition, false /* forSelectionPreservation */); ASSERT(index == indexComputedByVisiblePosition); #endif return index; }
Position InsertTextCommand::insertTab(const Position& pos) { Position insertPos = VisiblePosition(pos, DOWNSTREAM).deepEquivalent(); Node* node = insertPos.containerNode(); unsigned int offset = node->isTextNode() ? insertPos.offsetInContainerNode() : 0; // keep tabs coalesced in tab span if (isTabSpanTextNode(node)) { RefPtr<Text> textNode = downcast<Text>(node); insertTextIntoNode(textNode, offset, "\t"); return Position(textNode.release(), offset + 1); } // create new tab span RefPtr<Element> spanNode = createTabSpanElement(document()); // place it if (!is<Text>(*node)) { insertNodeAt(spanNode.get(), insertPos); } else { RefPtr<Text> textNode = downcast<Text>(node); if (offset >= textNode->length()) insertNodeAfter(spanNode, textNode.release()); else { // split node to make room for the span // NOTE: splitTextNode uses textNode for the // second node in the split, so we need to // insert the span before it. if (offset > 0) splitTextNode(textNode, offset); insertNodeBefore(spanNode, textNode.release()); } } // return the position following the new tab return lastPositionInNode(spanNode.get()); }
void InputMethodController::setCompositionFromExistingText(const Vector<CompositionUnderline>& underlines, unsigned compositionStart, unsigned compositionEnd) { Element* editable = m_frame.selection().rootEditableElement(); Position base = m_frame.selection().base().downstream(); Node* baseNode = base.anchorNode(); if (editable->firstChild() == baseNode && editable->lastChild() == baseNode && baseNode->isTextNode()) { m_compositionNode = nullptr; m_customCompositionUnderlines.clear(); if (base.anchorType() != Position::PositionIsOffsetInAnchor) return; if (!baseNode || baseNode != m_frame.selection().extent().anchorNode()) return; m_compositionNode = toText(baseNode); RefPtr<Range> range = PlainTextRange(compositionStart, compositionEnd).createRange(*editable); m_compositionStart = range->startOffset(); m_compositionEnd = range->endOffset(); m_customCompositionUnderlines = underlines; size_t numUnderlines = m_customCompositionUnderlines.size(); for (size_t i = 0; i < numUnderlines; ++i) { m_customCompositionUnderlines[i].startOffset += m_compositionStart; m_customCompositionUnderlines[i].endOffset += m_compositionStart; } // TODO(ojan): What was this for? Do we need it in sky since we // don't need to support legacy IMEs? if (baseNode->renderer()) baseNode->document().scheduleVisualUpdate(); return; } Editor::RevealSelectionScope revealSelectionScope(&editor()); SelectionOffsetsScope selectionOffsetsScope(this); setSelectionOffsets(PlainTextRange(compositionStart, compositionEnd)); setComposition(m_frame.selectedText(), underlines, 0, 0); }
// If this is the layoutObject for a first-letter pseudoNode then we have to look // at the node for the remaining text to find our content. Text* LayoutTextFragment::associatedTextNode() const { Node* node = this->firstLetterPseudoElement(); if (m_isRemainingTextLayoutObject || !node) { // If we don't have a node, then we aren't part of a first-letter pseudo // element, so use the actual node. Likewise, if we have a node, but // we're the remainingTextLayoutObject for a pseudo element use the real // text node. node = this->node(); } if (!node) return nullptr; if (node->isFirstLetterPseudoElement()) { FirstLetterPseudoElement* pseudo = toFirstLetterPseudoElement(node); LayoutObject* nextLayoutObject = FirstLetterPseudoElement::firstLetterTextLayoutObject(*pseudo); if (!nextLayoutObject) return nullptr; node = nextLayoutObject->node(); } return (node && node->isTextNode()) ? toText(node) : nullptr; }
void HTMLOptionElement::setText(const String &text, ExceptionCode& ec) { RefPtr<Node> protectFromMutationEvents(this); // Changing the text causes a recalc of a select's items, which will reset the selected // index to the first item if the select is single selection with a menu list. We attempt to // preserve the selected item. RefPtr<HTMLSelectElement> select = ownerSelectElement(); bool selectIsMenuList = select && select->usesMenuList(); int oldSelectedIndex = selectIsMenuList ? select->selectedIndex() : -1; // Handle the common special case where there's exactly 1 child node, and it's a text node. Node* child = firstChild(); if (child && child->isTextNode() && !child->nextSibling()) toText(child)->setData(text, ec); else { removeChildren(); appendChild(Text::create(document(), text), ec); } if (selectIsMenuList && select->selectedIndex() != oldSelectedIndex) select->setSelectedIndex(oldSelectedIndex); }
void InputMethodController::setCompositionFromExistingText(const Vector<CompositionUnderline>& underlines, unsigned compositionStart, unsigned compositionEnd) { Element* editable = frame().selection().rootEditableElement(); Position base = frame().selection().base().downstream(); Node* baseNode = base.anchorNode(); if (editable->firstChild() == baseNode && editable->lastChild() == baseNode && baseNode->isTextNode()) { m_compositionNode = nullptr; m_customCompositionUnderlines.clear(); if (base.anchorType() != Position::PositionIsOffsetInAnchor) return; if (!baseNode || baseNode != frame().selection().extent().anchorNode()) return; m_compositionNode = toText(baseNode); RefPtrWillBeRawPtr<Range> range = PlainTextRange(compositionStart, compositionEnd).createRange(*editable); if (!range) return; m_compositionStart = range->startOffset(); m_compositionEnd = range->endOffset(); m_customCompositionUnderlines = underlines; size_t numUnderlines = m_customCompositionUnderlines.size(); for (size_t i = 0; i < numUnderlines; ++i) { m_customCompositionUnderlines[i].startOffset += m_compositionStart; m_customCompositionUnderlines[i].endOffset += m_compositionStart; } if (baseNode->renderer()) baseNode->renderer()->setShouldDoFullPaintInvalidation(true); return; } Editor::RevealSelectionScope revealSelectionScope(&editor()); SelectionOffsetsScope selectionOffsetsScope(this); setSelectionOffsets(PlainTextRange(compositionStart, compositionEnd)); setComposition(frame().selectedText(), underlines, 0, 0); }
Position InsertTextCommand::insertTab(const Position& pos) { Position insertPos = VisiblePosition(pos, DOWNSTREAM).deepEquivalent(); Node *node = insertPos.node(); unsigned int offset = insertPos.offset(); // keep tabs coalesced in tab span if (isTabSpanTextNode(node)) { insertTextIntoNode(static_cast<Text *>(node), offset, "\t"); return Position(node, offset + 1); } // create new tab span RefPtr<Element> spanNode = createTabSpanElement(document()); // place it if (!node->isTextNode()) { insertNodeAt(spanNode.get(), node, offset); } else { Text *textNode = static_cast<Text *>(node); if (offset >= textNode->length()) { insertNodeAfter(spanNode.get(), textNode); } else { // split node to make room for the span // NOTE: splitTextNode uses textNode for the // second node in the split, so we need to // insert the span before it. if (offset > 0) splitTextNode(textNode, offset); insertNodeBefore(spanNode.get(), textNode); } } // return the position following the new tab return Position(spanNode->lastChild(), spanNode->lastChild()->caretMaxOffset()); }
void WebPage::findZoomableAreaForPoint(const IntPoint& point, const IntSize& area) { UNUSED_PARAM(area); Frame* mainframe = m_mainFrame->coreFrame(); HitTestResult result = mainframe->eventHandler().hitTestResultAtPoint(mainframe->view()->windowToContents(point), HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::IgnoreClipping | HitTestRequest::DisallowShadowContent); Node* node = result.innerNode(); if (!node) return; IntRect zoomableArea = node->pixelSnappedBoundingBox(); while (true) { bool found = !node->isTextNode() && !node->isShadowRoot(); // No candidate found, bail out. if (!found && !node->parentNode()) return; // Candidate found, and it is a better candidate than its parent. // NB: A parent is considered a better candidate iff the node is // contained by it and it is the only child. if (found && (!node->parentNode() || node->parentNode()->childNodeCount() != 1)) break; node = node->parentNode(); zoomableArea.unite(node->pixelSnappedBoundingBox()); } if (node->document().frame() && node->document().frame()->view()) { const ScrollView* view = node->document().frame()->view(); zoomableArea = view->contentsToWindow(zoomableArea); } send(Messages::WebPageProxy::DidFindZoomableArea(point, zoomableArea)); }
void HTMLTextAreaElement::setDefaultValue(const String& defaultValue) { RefPtrWillBeRawPtr<Node> protectFromMutationEvents(this); // To preserve comments, remove only the text nodes, then add a single text node. WillBeHeapVector<RefPtrWillBeMember<Node>> textNodes; for (Node* n = firstChild(); n; n = n->nextSibling()) { if (n->isTextNode()) textNodes.append(n); } size_t size = textNodes.size(); for (size_t i = 0; i < size; ++i) removeChild(textNodes[i].get(), IGNORE_EXCEPTION); // Normalize line endings. String value = defaultValue; value.replace("\r\n", "\n"); value.replace('\r', '\n'); insertBefore(document().createTextNode(value), firstChild(), IGNORE_EXCEPTION); if (!m_isDirty) setNonDirtyValue(value); }
void HTMLTextAreaElement::setDefaultValue(const String& defaultValue) { // To preserve comments, remove only the text nodes, then add a single text node. Vector<RefPtr<Node> > textNodes; for (Node* n = firstChild(); n; n = n->nextSibling()) { if (n->isTextNode()) textNodes.append(n); } ExceptionCode ec; size_t size = textNodes.size(); for (size_t i = 0; i < size; ++i) removeChild(textNodes[i].get(), ec); // Normalize line endings. String value = defaultValue; value.replace("\r\n", "\n"); value.replace('\r', '\n'); insertBefore(document()->createTextNode(value), firstChild(), ec); if (!m_isDirty) setNonDirtyValue(value); }
String SmartClip::extractTextFromNode(Node* node) { // Science has proven that no text nodes are ever positioned at y == -99999. int prevYPos = -99999; StringBuilder result; for (Node* currentNode = node; currentNode; currentNode = NodeTraversal::next(*currentNode, node)) { RenderStyle* style = currentNode->computedStyle(); if (style && style->userSelect() == SELECT_NONE) continue; if (Node* nodeFromFrame = nodeInsideFrame(currentNode)) result.append(extractTextFromNode(nodeFromFrame)); IntRect nodeRect = currentNode->pixelSnappedBoundingBox(); if (currentNode->renderer() && !nodeRect.isEmpty()) { if (currentNode->isTextNode()) { String nodeValue = currentNode->nodeValue(); // It's unclear why we blacklist solitary "\n" node values. // Maybe we're trying to ignore <br> tags somehow? if (nodeValue == "\n") nodeValue = ""; if (nodeRect.y() != prevYPos) { prevYPos = nodeRect.y(); result.append('\n'); } result.append(nodeValue); } } } return result.toString(); }
void BreakBlockquoteCommand::doApply(EditingState* editingState) { if (endingSelection().isNone()) return; // Delete the current selection. if (endingSelection().isRange()) { deleteSelection(editingState, false, false); if (editingState->isAborted()) return; } // This is a scenario that should never happen, but we want to // make sure we don't dereference a null pointer below. DCHECK(!endingSelection().isNone()); if (endingSelection().isNone()) return; VisiblePosition visiblePos = endingSelection().visibleStart(); // pos is a position equivalent to the caret. We use downstream() so that pos will // be in the first node that we need to move (there are a few exceptions to this, see below). Position pos = mostForwardCaretPosition(endingSelection().start()); // Find the top-most blockquote from the start. HTMLQuoteElement* topBlockquote = toHTMLQuoteElement(highestEnclosingNodeOfType(pos, isMailHTMLBlockquoteElement)); if (!topBlockquote || !topBlockquote->parentNode()) return; HTMLBRElement* breakElement = HTMLBRElement::create(document()); bool isLastVisPosInNode = isLastVisiblePositionInNode(visiblePos, topBlockquote); // If the position is at the beginning of the top quoted content, we don't need to break the quote. // Instead, insert the break before the blockquote, unless the position is as the end of the the quoted content. if (isFirstVisiblePositionInNode(visiblePos, topBlockquote) && !isLastVisPosInNode) { insertNodeBefore(breakElement, topBlockquote, editingState); if (editingState->isAborted()) return; setEndingSelection(VisibleSelection(Position::beforeNode(breakElement), TextAffinity::Downstream, endingSelection().isDirectional())); rebalanceWhitespace(); return; } // Insert a break after the top blockquote. insertNodeAfter(breakElement, topBlockquote, editingState); if (editingState->isAborted()) return; // If we're inserting the break at the end of the quoted content, we don't need to break the quote. if (isLastVisPosInNode) { setEndingSelection(VisibleSelection(Position::beforeNode(breakElement), TextAffinity::Downstream, endingSelection().isDirectional())); rebalanceWhitespace(); return; } // Don't move a line break just after the caret. Doing so would create an extra, empty paragraph // in the new blockquote. if (lineBreakExistsAtVisiblePosition(visiblePos)) { pos = nextPositionOf(pos, PositionMoveType::GraphemeCluster); } // Adjust the position so we don't split at the beginning of a quote. while (isFirstVisiblePositionInNode(createVisiblePosition(pos), toHTMLQuoteElement(enclosingNodeOfType(pos, isMailHTMLBlockquoteElement)))) { pos = previousPositionOf(pos, PositionMoveType::GraphemeCluster); } // startNode is the first node that we need to move to the new blockquote. Node* startNode = pos.anchorNode(); DCHECK(startNode); // Split at pos if in the middle of a text node. if (startNode->isTextNode()) { Text* textNode = toText(startNode); int textOffset = pos.computeOffsetInContainerNode(); if ((unsigned)textOffset >= textNode->length()) { startNode = NodeTraversal::next(*startNode); DCHECK(startNode); } else if (textOffset > 0) { splitTextNode(textNode, textOffset); } } else if (pos.computeEditingOffset() > 0) { Node* childAtOffset = NodeTraversal::childAt(*startNode, pos.computeEditingOffset()); startNode = childAtOffset ? childAtOffset : NodeTraversal::next(*startNode); DCHECK(startNode); } // If there's nothing inside topBlockquote to move, we're finished. if (!startNode->isDescendantOf(topBlockquote)) { setEndingSelection(VisibleSelection(createVisiblePosition(firstPositionInOrBeforeNode(startNode)), endingSelection().isDirectional())); return; } // Build up list of ancestors in between the start node and the top blockquote. HeapVector<Member<Element>> ancestors; for (Element* node = startNode->parentElement(); node && node != topBlockquote; node = node->parentElement()) ancestors.append(node); // Insert a clone of the top blockquote after the break. Element* clonedBlockquote = topBlockquote->cloneElementWithoutChildren(); insertNodeAfter(clonedBlockquote, breakElement, editingState); if (editingState->isAborted()) return; // Clone startNode's ancestors into the cloned blockquote. // On exiting this loop, clonedAncestor is the lowest ancestor // that was cloned (i.e. the clone of either ancestors.last() // or clonedBlockquote if ancestors is empty). Element* clonedAncestor = clonedBlockquote; for (size_t i = ancestors.size(); i != 0; --i) { Element* clonedChild = ancestors[i - 1]->cloneElementWithoutChildren(); // Preserve list item numbering in cloned lists. if (isHTMLOListElement(*clonedChild)) { Node* listChildNode = i > 1 ? ancestors[i - 2].get() : startNode; // The first child of the cloned list might not be a list item element, // find the first one so that we know where to start numbering. while (listChildNode && !isHTMLLIElement(*listChildNode)) listChildNode = listChildNode->nextSibling(); if (isListItem(listChildNode)) setNodeAttribute(clonedChild, startAttr, AtomicString::number(toLayoutListItem(listChildNode->layoutObject())->value())); } appendNode(clonedChild, clonedAncestor, editingState); if (editingState->isAborted()) return; clonedAncestor = clonedChild; } moveRemainingSiblingsToNewParent(startNode, 0, clonedAncestor, editingState); if (editingState->isAborted()) return; if (!ancestors.isEmpty()) { // Split the tree up the ancestor chain until the topBlockquote // Throughout this loop, clonedParent is the clone of ancestor's parent. // This is so we can clone ancestor's siblings and place the clones // into the clone corresponding to the ancestor's parent. Element* ancestor = nullptr; Element* clonedParent = nullptr; for (ancestor = ancestors.first(), clonedParent = clonedAncestor->parentElement(); ancestor && ancestor != topBlockquote; ancestor = ancestor->parentElement(), clonedParent = clonedParent->parentElement()) { moveRemainingSiblingsToNewParent(ancestor->nextSibling(), 0, clonedParent, editingState); if (editingState->isAborted()) return; } // If the startNode's original parent is now empty, remove it Element* originalParent = ancestors.first().get(); if (!originalParent->hasChildren()) { removeNode(originalParent, editingState); if (editingState->isAborted()) return; } } // Make sure the cloned block quote renders. addBlockPlaceholderIfNeeded(clonedBlockquote, editingState); if (editingState->isAborted()) return; // Put the selection right before the break. setEndingSelection(VisibleSelection(Position::beforeNode(breakElement), TextAffinity::Downstream, endingSelection().isDirectional())); rebalanceWhitespace(); }
bool MathMLElement::isPhrasingContent(const Node& node) const { // Phrasing content is described in the HTML 5 specification: // http://www.w3.org/TR/html5/dom.html#phrasing-content. if (!node.isElementNode()) return node.isTextNode(); if (is<MathMLElement>(node)) { auto& mathmlElement = downcast<MathMLElement>(node); return is<MathMLMathElement>(mathmlElement); } if (is<SVGElement>(node)) { auto& svgElement = downcast<SVGElement>(node); return is<SVGSVGElement>(svgElement); } if (is<HTMLElement>(node)) { // FIXME: add the <data> and <time> tags when they are implemented. auto& htmlElement = downcast<HTMLElement>(node); return htmlElement.hasTagName(HTMLNames::aTag) || htmlElement.hasTagName(HTMLNames::abbrTag) || (htmlElement.hasTagName(HTMLNames::areaTag) && ancestorsOfType<HTMLMapElement>(htmlElement).first()) || htmlElement.hasTagName(HTMLNames::audioTag) || htmlElement.hasTagName(HTMLNames::bTag) || htmlElement.hasTagName(HTMLNames::bdiTag) || htmlElement.hasTagName(HTMLNames::bdoTag) || htmlElement.hasTagName(HTMLNames::brTag) || htmlElement.hasTagName(HTMLNames::buttonTag) || htmlElement.hasTagName(HTMLNames::canvasTag) || htmlElement.hasTagName(HTMLNames::citeTag) || htmlElement.hasTagName(HTMLNames::codeTag) || htmlElement.hasTagName(HTMLNames::datalistTag) || htmlElement.hasTagName(HTMLNames::delTag) || htmlElement.hasTagName(HTMLNames::dfnTag) || htmlElement.hasTagName(HTMLNames::emTag) || htmlElement.hasTagName(HTMLNames::embedTag) || htmlElement.hasTagName(HTMLNames::iTag) || htmlElement.hasTagName(HTMLNames::iframeTag) || htmlElement.hasTagName(HTMLNames::imgTag) || htmlElement.hasTagName(HTMLNames::inputTag) || htmlElement.hasTagName(HTMLNames::insTag) || htmlElement.hasTagName(HTMLNames::kbdTag) || htmlElement.hasTagName(HTMLNames::keygenTag) || htmlElement.hasTagName(HTMLNames::labelTag) || htmlElement.hasTagName(HTMLNames::mapTag) || htmlElement.hasTagName(HTMLNames::markTag) || htmlElement.hasTagName(HTMLNames::meterTag) || htmlElement.hasTagName(HTMLNames::noscriptTag) || htmlElement.hasTagName(HTMLNames::objectTag) || htmlElement.hasTagName(HTMLNames::outputTag) || htmlElement.hasTagName(HTMLNames::progressTag) || htmlElement.hasTagName(HTMLNames::qTag) || htmlElement.hasTagName(HTMLNames::rubyTag) || htmlElement.hasTagName(HTMLNames::sTag) || htmlElement.hasTagName(HTMLNames::sampTag) || htmlElement.hasTagName(HTMLNames::scriptTag) || htmlElement.hasTagName(HTMLNames::selectTag) || htmlElement.hasTagName(HTMLNames::smallTag) || htmlElement.hasTagName(HTMLNames::spanTag) || htmlElement.hasTagName(HTMLNames::strongTag) || htmlElement.hasTagName(HTMLNames::subTag) || htmlElement.hasTagName(HTMLNames::supTag) || htmlElement.hasTagName(HTMLNames::templateTag) || htmlElement.hasTagName(HTMLNames::textareaTag) || htmlElement.hasTagName(HTMLNames::uTag) || htmlElement.hasTagName(HTMLNames::varTag) || htmlElement.hasTagName(HTMLNames::videoTag) || htmlElement.hasTagName(HTMLNames::wbrTag); } return false; }
void InputMethodController::setComposition(const String& text, const Vector<CompositionUnderline>& underlines, unsigned selectionStart, unsigned selectionEnd) { UserTypingGestureIndicator typingGestureIndicator(m_frame); 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 m_frame->document()->updateStyleIfNeeded(); selectComposition(); if (m_frame->selection().isNone()) return; if (Element* target = m_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. // m_compositionNode == 0 && !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. m_compositionNode != 0 && !text.isEmpty(). // 3. Canceling the ongoing composition. // Send a compositionend event when function deletes the existing composition node, i.e. // m_compositionNode != 0 && test.isEmpty(). RefPtr<CompositionEvent> event; if (!m_compositionNode) { // 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(eventNames().compositionstartEvent, m_frame->domWindow(), m_frame->selectedText())); event = CompositionEvent::create(eventNames().compositionupdateEvent, m_frame->domWindow(), text); } } else { if (!text.isEmpty()) event = CompositionEvent::create(eventNames().compositionupdateEvent, m_frame->domWindow(), text); else event = CompositionEvent::create(eventNames().compositionendEvent, m_frame->domWindow(), text); } if (event.get()) target->dispatchEvent(event, IGNORE_EXCEPTION); } // 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(m_frame->document()); TypingCommand::deleteSelection(*m_frame->document(), TypingCommand::PreventSpellChecking); } m_compositionNode = 0; m_customCompositionUnderlines.clear(); if (!text.isEmpty()) { ASSERT(m_frame->document()); TypingCommand::insertText(*m_frame->document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionUpdate); // Find out what node has the composition now. Position base = m_frame->selection().base().downstream(); Position extent = m_frame->selection().extent(); Node* baseNode = base.deprecatedNode(); unsigned baseOffset = base.deprecatedEditingOffset(); Node* extentNode = extent.deprecatedNode(); unsigned extentOffset = extent.deprecatedEditingOffset(); if (baseNode && baseNode == extentNode && baseNode->isTextNode() && baseOffset + text.length() == extentOffset) { m_compositionNode = toText(baseNode); m_compositionStart = baseOffset; m_compositionEnd = extentOffset; m_customCompositionUnderlines = underlines; size_t numUnderlines = m_customCompositionUnderlines.size(); for (size_t i = 0; i < numUnderlines; ++i) { m_customCompositionUnderlines[i].startOffset += baseOffset; m_customCompositionUnderlines[i].endOffset += baseOffset; } if (baseNode->renderer()) baseNode->renderer()->repaint(); unsigned start = std::min(baseOffset + selectionStart, extentOffset); unsigned end = std::min(std::max(start, baseOffset + selectionEnd), extentOffset); RefPtr<Range> selectedRange = Range::create(baseNode->document(), baseNode, start, baseNode, end); m_frame->selection().setSelectedRange(selectedRange.get(), DOWNSTREAM, false); } } }
static VisiblePosition previousBoundary(const VisiblePosition& c, BoundarySearchFunction searchFunction) { Position pos = c.deepEquivalent(); Node *n = pos.node(); if (!n) return VisiblePosition(); Document *d = n->document(); Node *de = d->documentElement(); if (!de) return VisiblePosition(); Node *boundary = n->enclosingBlockFlowElement(); if (!boundary) return VisiblePosition(); bool isContentEditable = boundary->isContentEditable(); while (boundary && boundary != de && boundary->parentNode() && isContentEditable == boundary->parentNode()->isContentEditable()) boundary = boundary->parentNode(); Position start = rangeCompliantEquivalent(Position(boundary, 0)); Position end = rangeCompliantEquivalent(pos); RefPtr<Range> searchRange = Range::create(d); Vector<UChar, 1024> string; unsigned suffixLength = 0; ExceptionCode ec = 0; if (requiresContextForWordBoundary(c.characterBefore())) { RefPtr<Range> forwardsScanRange(d->createRange()); forwardsScanRange->setEndAfter(boundary, ec); forwardsScanRange->setStart(end.node(), end.deprecatedEditingOffset(), ec); TextIterator forwardsIterator(forwardsScanRange.get()); while (!forwardsIterator.atEnd()) { const UChar* characters = forwardsIterator.characters(); int length = forwardsIterator.length(); int i = endOfFirstWordBoundaryContext(characters, length); string.append(characters, i); suffixLength += i; if (i < length) break; forwardsIterator.advance(); } } searchRange->setStart(start.node(), start.deprecatedEditingOffset(), ec); searchRange->setEnd(end.node(), end.deprecatedEditingOffset(), ec); ASSERT(!ec); if (ec) return VisiblePosition(); SimplifiedBackwardsTextIterator it(searchRange.get()); unsigned next = 0; bool inTextSecurityMode = start.node() && start.node()->renderer() && start.node()->renderer()->style()->textSecurity() != TSNONE; bool needMoreContext = false; while (!it.atEnd()) { // iterate to get chunks until the searchFunction returns a non-zero value. if (!inTextSecurityMode) string.prepend(it.characters(), it.length()); else { // Treat bullets used in the text security mode as regular characters when looking for boundaries String iteratorString(it.characters(), it.length()); iteratorString = iteratorString.impl()->secure('x'); string.prepend(iteratorString.characters(), iteratorString.length()); } next = searchFunction(string.data(), string.size(), string.size() - suffixLength, MayHaveMoreContext, needMoreContext); if (next != 0) break; it.advance(); } if (needMoreContext) { // The last search returned the beginning of the buffer and asked for more context, // but there is no earlier text. Force a search with what's available. next = searchFunction(string.data(), string.size(), string.size() - suffixLength, DontHaveMoreContext, needMoreContext); ASSERT(!needMoreContext); } if (it.atEnd() && next == 0) { pos = it.range()->startPosition(); } else if (next != 0) { Node *node = it.range()->startContainer(ec); if ((node->isTextNode() && static_cast<int>(next) <= node->maxCharacterOffset()) || (node->renderer() && node->renderer()->isBR() && !next)) // The next variable contains a usable index into a text node pos = Position(node, next); else { // Use the character iterator to translate the next value into a DOM position. BackwardsCharacterIterator charIt(searchRange.get()); charIt.advance(string.size() - suffixLength - next); pos = charIt.range()->endPosition(); } } return VisiblePosition(pos, DOWNSTREAM); }
String Frame::searchForLabelsBeforeElement(const Vector<String>& labels, Element* element, size_t* resultDistance, bool* resultIsInCellAbove) { OwnPtr<RegularExpression> regExp(createRegExpForLabels(labels)); // We stop searching after we've seen this many chars const unsigned int charsSearchedThreshold = 500; // This is the absolute max we search. We allow a little more slop than // charsSearchedThreshold, to make it more likely that we'll search whole nodes. const unsigned int maxCharsSearched = 600; // If the starting element is within a table, the cell that contains it HTMLTableCellElement* startingTableCell = 0; bool searchedCellAbove = false; if (resultDistance) *resultDistance = notFound; if (resultIsInCellAbove) *resultIsInCellAbove = false; // walk backwards in the node tree, until another element, or form, or end of tree int unsigned lengthSearched = 0; Node* n; for (n = element->traversePreviousNode(); n && lengthSearched < charsSearchedThreshold; n = n->traversePreviousNode()) { if (n->hasTagName(formTag) || (n->isHTMLElement() && static_cast<Element*>(n)->isFormControlElement())) { // We hit another form element or the start of the form - bail out break; } else if (n->hasTagName(tdTag) && !startingTableCell) { startingTableCell = static_cast<HTMLTableCellElement*>(n); } else if (n->hasTagName(trTag) && startingTableCell) { String result = searchForLabelsAboveCell(regExp.get(), startingTableCell, resultDistance); if (!result.isEmpty()) { if (resultIsInCellAbove) *resultIsInCellAbove = true; return result; } searchedCellAbove = true; } else if (n->isTextNode() && n->renderer() && n->renderer()->style()->visibility() == VISIBLE) { // For each text chunk, run the regexp String nodeString = n->nodeValue(); // add 100 for slop, to make it more likely that we'll search whole nodes if (lengthSearched + nodeString.length() > maxCharsSearched) nodeString = nodeString.right(charsSearchedThreshold - lengthSearched); int pos = regExp->searchRev(nodeString); if (pos >= 0) { if (resultDistance) *resultDistance = lengthSearched; return nodeString.substring(pos, regExp->matchedLength()); } lengthSearched += nodeString.length(); } } // If we started in a cell, but bailed because we found the start of the form or the // previous element, we still might need to search the row above us for a label. if (startingTableCell && !searchedCellAbove) { String result = searchForLabelsAboveCell(regExp.get(), startingTableCell, resultDistance); if (!result.isEmpty()) { if (resultIsInCellAbove) *resultIsInCellAbove = true; return result; } } return String(); }
void BreakBlockquoteCommand::doApply() { if (endingSelection().isNone()) return; // Delete the current selection. if (endingSelection().isRange()) deleteSelection(false, false); // This is a scenario that should never happen, but we want to // make sure we don't dereference a null pointer below. ASSERT(!endingSelection().isNone()); if (endingSelection().isNone()) return; VisiblePosition visiblePos = endingSelection().visibleStart(); // pos is a position equivalent to the caret. We use downstream() so that pos will // be in the first node that we need to move (there are a few exceptions to this, see below). Position pos = endingSelection().start().downstream(); // Find the top-most blockquote from the start. Node* topBlockquote = highestEnclosingNodeOfType(pos, isMailBlockquote); if (!topBlockquote || !topBlockquote->parentNode() || !topBlockquote->isElementNode()) return; RefPtr<Element> breakNode = createBreakElement(document()); bool isLastVisPosInNode = isLastVisiblePositionInNode(visiblePos, topBlockquote); // If the position is at the beginning of the top quoted content, we don't need to break the quote. // Instead, insert the break before the blockquote, unless the position is as the end of the the quoted content. if (isFirstVisiblePositionInNode(visiblePos, topBlockquote) && !isLastVisPosInNode) { insertNodeBefore(breakNode.get(), topBlockquote); setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.get()), DOWNSTREAM, endingSelection().isDirectional())); rebalanceWhitespace(); return; } // Insert a break after the top blockquote. insertNodeAfter(breakNode.get(), topBlockquote); // If we're inserting the break at the end of the quoted content, we don't need to break the quote. if (isLastVisPosInNode) { setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.get()), DOWNSTREAM, endingSelection().isDirectional())); rebalanceWhitespace(); return; } // Don't move a line break just after the caret. Doing so would create an extra, empty paragraph // in the new blockquote. if (lineBreakExistsAtVisiblePosition(visiblePos)) pos = pos.next(); // Adjust the position so we don't split at the beginning of a quote. while (isFirstVisiblePositionInNode(VisiblePosition(pos), enclosingNodeOfType(pos, isMailBlockquote))) pos = pos.previous(); // startNode is the first node that we need to move to the new blockquote. Node* startNode = pos.deprecatedNode(); // Split at pos if in the middle of a text node. if (startNode->isTextNode()) { Text* textNode = toText(startNode); if ((unsigned)pos.deprecatedEditingOffset() >= textNode->length()) { startNode = NodeTraversal::next(startNode); ASSERT(startNode); } else if (pos.deprecatedEditingOffset() > 0) splitTextNode(textNode, pos.deprecatedEditingOffset()); } else if (pos.deprecatedEditingOffset() > 0) { Node* childAtOffset = startNode->childNode(pos.deprecatedEditingOffset()); startNode = childAtOffset ? childAtOffset : NodeTraversal::next(startNode); ASSERT(startNode); } // If there's nothing inside topBlockquote to move, we're finished. if (!startNode->isDescendantOf(topBlockquote)) { setEndingSelection(VisibleSelection(VisiblePosition(firstPositionInOrBeforeNode(startNode)), endingSelection().isDirectional())); return; } // Build up list of ancestors in between the start node and the top blockquote. Vector<RefPtr<Element> > ancestors; for (Element* node = startNode->parentElement(); node && node != topBlockquote; node = node->parentElement()) ancestors.append(node); // Insert a clone of the top blockquote after the break. RefPtr<Element> clonedBlockquote = toElement(topBlockquote)->cloneElementWithoutChildren(); insertNodeAfter(clonedBlockquote.get(), breakNode.get()); // Clone startNode's ancestors into the cloned blockquote. // On exiting this loop, clonedAncestor is the lowest ancestor // that was cloned (i.e. the clone of either ancestors.last() // or clonedBlockquote if ancestors is empty). RefPtr<Element> clonedAncestor = clonedBlockquote; for (size_t i = ancestors.size(); i != 0; --i) { RefPtr<Element> clonedChild = ancestors[i - 1]->cloneElementWithoutChildren(); // Preserve list item numbering in cloned lists. if (clonedChild->isElementNode() && clonedChild->hasTagName(olTag)) { Node* listChildNode = i > 1 ? ancestors[i - 2].get() : startNode; // The first child of the cloned list might not be a list item element, // find the first one so that we know where to start numbering. while (listChildNode && !listChildNode->hasTagName(liTag)) listChildNode = listChildNode->nextSibling(); if (listChildNode && listChildNode->renderer() && listChildNode->renderer()->isListItem()) setNodeAttribute(clonedChild, startAttr, String::number(toRenderListItem(listChildNode->renderer())->value())); } appendNode(clonedChild.get(), clonedAncestor.get()); clonedAncestor = clonedChild; } moveRemainingSiblingsToNewParent(startNode, 0, clonedAncestor); if (!ancestors.isEmpty()) { // Split the tree up the ancestor chain until the topBlockquote // Throughout this loop, clonedParent is the clone of ancestor's parent. // This is so we can clone ancestor's siblings and place the clones // into the clone corresponding to the ancestor's parent. RefPtr<Element> ancestor; RefPtr<Element> clonedParent; for (ancestor = ancestors.first(), clonedParent = clonedAncestor->parentElement(); ancestor && ancestor != topBlockquote; ancestor = ancestor->parentElement(), clonedParent = clonedParent->parentElement()) moveRemainingSiblingsToNewParent(ancestor->nextSibling(), 0, clonedParent); // If the startNode's original parent is now empty, remove it Node* originalParent = ancestors.first().get(); if (!originalParent->hasChildNodes()) removeNode(originalParent); } // Make sure the cloned block quote renders. addBlockPlaceholderIfNeeded(clonedBlockquote.get()); // Put the selection right before the break. setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.get()), DOWNSTREAM, endingSelection().isDirectional())); rebalanceWhitespace(); }
void InsertParagraphSeparatorCommand::doApply() { if (!endingSelection().isNonOrphanedCaretOrRange()) return; Position insertionPosition = endingSelection().start(); EAffinity affinity = endingSelection().affinity(); // Delete the current selection. if (endingSelection().isRange()) { calculateStyleBeforeInsertion(insertionPosition); deleteSelection(false, true); insertionPosition = endingSelection().start(); affinity = endingSelection().affinity(); } // FIXME: The parentAnchoredEquivalent conversion needs to be moved into enclosingBlock. RefPtr<Element> startBlock = enclosingBlock(insertionPosition.parentAnchoredEquivalent().containerNode()); Position canonicalPos = VisiblePosition(insertionPosition).deepEquivalent(); if (!startBlock || !startBlock->nonShadowBoundaryParentNode() || isTableCell(startBlock.get()) || isHTMLFormElement(startBlock.get()) // FIXME: If the node is hidden, we don't have a canonical position so we will do the wrong thing for tables and <hr>. https://bugs.webkit.org/show_bug.cgi?id=40342 || (!canonicalPos.isNull() && canonicalPos.deprecatedNode()->renderer() && canonicalPos.deprecatedNode()->renderer()->isTable()) || (!canonicalPos.isNull() && canonicalPos.deprecatedNode()->hasTagName(hrTag))) { applyCommandToComposite(InsertLineBreakCommand::create(document())); return; } // Use the leftmost candidate. insertionPosition = insertionPosition.upstream(); if (!insertionPosition.isCandidate()) insertionPosition = insertionPosition.downstream(); // Adjust the insertion position after the delete insertionPosition = positionAvoidingSpecialElementBoundary(insertionPosition); VisiblePosition visiblePos(insertionPosition, affinity); calculateStyleBeforeInsertion(insertionPosition); //--------------------------------------------------------------------- // Handle special case of typing return on an empty list item if (breakOutOfEmptyListItem()) return; //--------------------------------------------------------------------- // Prepare for more general cases. bool isFirstInBlock = isStartOfBlock(visiblePos); bool isLastInBlock = isEndOfBlock(visiblePos); bool nestNewBlock = false; // Create block to be inserted. RefPtr<Element> blockToInsert; if (startBlock->isRootEditableElement()) { blockToInsert = createDefaultParagraphElement(&document()); nestNewBlock = true; } else if (shouldUseDefaultParagraphElement(startBlock.get())) blockToInsert = createDefaultParagraphElement(&document()); else blockToInsert = startBlock->cloneElementWithoutChildren(); //--------------------------------------------------------------------- // Handle case when position is in the last visible position in its block, // including when the block is empty. if (isLastInBlock) { if (nestNewBlock) { if (isFirstInBlock && !lineBreakExistsAtVisiblePosition(visiblePos)) { // The block is empty. Create an empty block to // represent the paragraph that we're leaving. RefPtr<Element> extraBlock = createDefaultParagraphElement(&document()); appendNode(extraBlock, startBlock); appendBlockPlaceholder(extraBlock); } appendNode(blockToInsert, startBlock); } else { // We can get here if we pasted a copied portion of a blockquote with a newline at the end and are trying to paste it // into an unquoted area. We then don't want the newline within the blockquote or else it will also be quoted. if (m_pasteBlockqutoeIntoUnquotedArea) { if (Node* highestBlockquote = highestEnclosingNodeOfType(canonicalPos, &isMailBlockquote)) startBlock = toElement(highestBlockquote); } // Most of the time we want to stay at the nesting level of the startBlock (e.g., when nesting within lists). However, // for div nodes, this can result in nested div tags that are hard to break out of. Element* siblingNode = startBlock.get(); if (blockToInsert->hasTagName(divTag)) siblingNode = highestVisuallyEquivalentDivBelowRoot(startBlock.get()); insertNodeAfter(blockToInsert, siblingNode); } // Recreate the same structure in the new paragraph. Vector<RefPtr<Element> > ancestors; getAncestorsInsideBlock(positionOutsideTabSpan(insertionPosition).deprecatedNode(), startBlock.get(), ancestors); RefPtr<Element> parent = cloneHierarchyUnderNewBlock(ancestors, blockToInsert); appendBlockPlaceholder(parent); setEndingSelection(VisibleSelection(firstPositionInNode(parent.get()), DOWNSTREAM, endingSelection().isDirectional())); return; } //--------------------------------------------------------------------- // Handle case when position is in the first visible position in its block, and // similar case where previous position is in another, presumeably nested, block. if (isFirstInBlock || !inSameBlock(visiblePos, visiblePos.previous())) { Node *refNode; insertionPosition = positionOutsideTabSpan(insertionPosition); if (isFirstInBlock && !nestNewBlock) refNode = startBlock.get(); else if (isFirstInBlock && nestNewBlock) { // startBlock should always have children, otherwise isLastInBlock would be true and it's handled above. ASSERT(startBlock->firstChild()); refNode = startBlock->firstChild(); } else if (insertionPosition.deprecatedNode() == startBlock && nestNewBlock) { refNode = startBlock->childNode(insertionPosition.deprecatedEditingOffset()); ASSERT(refNode); // must be true or we'd be in the end of block case } else refNode = insertionPosition.deprecatedNode(); // find ending selection position easily before inserting the paragraph insertionPosition = insertionPosition.downstream(); insertNodeBefore(blockToInsert, refNode); // Recreate the same structure in the new paragraph. Vector<RefPtr<Element> > ancestors; getAncestorsInsideBlock(positionAvoidingSpecialElementBoundary(positionOutsideTabSpan(insertionPosition)).deprecatedNode(), startBlock.get(), ancestors); appendBlockPlaceholder(cloneHierarchyUnderNewBlock(ancestors, blockToInsert)); // In this case, we need to set the new ending selection. setEndingSelection(VisibleSelection(insertionPosition, DOWNSTREAM, endingSelection().isDirectional())); return; } //--------------------------------------------------------------------- // Handle the (more complicated) general case, // All of the content in the current block after visiblePos is // about to be wrapped in a new paragraph element. Add a br before // it if visiblePos is at the start of a paragraph so that the // content will move down a line. if (isStartOfParagraph(visiblePos)) { RefPtr<Element> br = createBreakElement(&document()); insertNodeAt(br.get(), insertionPosition); insertionPosition = positionInParentAfterNode(br.get()); // If the insertion point is a break element, there is nothing else // we need to do. if (visiblePos.deepEquivalent().anchorNode()->renderer()->isBR()) { setEndingSelection(VisibleSelection(insertionPosition, DOWNSTREAM, endingSelection().isDirectional())); return; } } // Move downstream. Typing style code will take care of carrying along the // style of the upstream position. insertionPosition = insertionPosition.downstream(); // At this point, the insertionPosition's node could be a container, and we want to make sure we include // all of the correct nodes when building the ancestor list. So this needs to be the deepest representation of the position // before we walk the DOM tree. insertionPosition = positionOutsideTabSpan(VisiblePosition(insertionPosition).deepEquivalent()); // If the returned position lies either at the end or at the start of an element that is ignored by editing // we should move to its upstream or downstream position. if (editingIgnoresContent(insertionPosition.deprecatedNode())) { if (insertionPosition.atLastEditingPositionForNode()) insertionPosition = insertionPosition.downstream(); else if (insertionPosition.atFirstEditingPositionForNode()) insertionPosition = insertionPosition.upstream(); } // Make sure we do not cause a rendered space to become unrendered. // FIXME: We need the affinity for pos, but pos.downstream() does not give it Position leadingWhitespace = insertionPosition.leadingWhitespacePosition(VP_DEFAULT_AFFINITY); // FIXME: leadingWhitespacePosition is returning the position before preserved newlines for positions // after the preserved newline, causing the newline to be turned into a nbsp. if (leadingWhitespace.isNotNull() && leadingWhitespace.deprecatedNode()->isTextNode()) { Text* textNode = toText(leadingWhitespace.deprecatedNode()); ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace()); replaceTextInNodePreservingMarkers(textNode, leadingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); } // Split at pos if in the middle of a text node. Position positionAfterSplit; if (insertionPosition.anchorType() == Position::PositionIsOffsetInAnchor && insertionPosition.containerNode()->isTextNode()) { RefPtr<Text> textNode = toText(insertionPosition.containerNode()); bool atEnd = static_cast<unsigned>(insertionPosition.offsetInContainerNode()) >= textNode->length(); if (insertionPosition.deprecatedEditingOffset() > 0 && !atEnd) { splitTextNode(textNode, insertionPosition.offsetInContainerNode()); positionAfterSplit = firstPositionInNode(textNode.get()); if (!textNode->previousSibling()) return; // Bail out if mutation events detachd the split text node. insertionPosition.moveToPosition(textNode->previousSibling(), insertionPosition.offsetInContainerNode()); visiblePos = VisiblePosition(insertionPosition); } } // If we got detached due to mutation events, just bail out. if (!startBlock->parentNode()) return; // Put the added block in the tree. if (nestNewBlock) appendNode(blockToInsert.get(), startBlock); else insertNodeAfter(blockToInsert.get(), startBlock); document().updateLayoutIgnorePendingStylesheets(); // If the paragraph separator was inserted at the end of a paragraph, an empty line must be // created. All of the nodes, starting at visiblePos, are about to be added to the new paragraph // element. If the first node to be inserted won't be one that will hold an empty line open, add a br. if (isEndOfParagraph(visiblePos) && !lineBreakExistsAtVisiblePosition(visiblePos)) appendNode(createBreakElement(&document()).get(), blockToInsert.get()); // Move the start node and the siblings of the start node. if (VisiblePosition(insertionPosition) != VisiblePosition(positionBeforeNode(blockToInsert.get()))) { Node* n; if (insertionPosition.containerNode() == startBlock) n = insertionPosition.computeNodeAfterPosition(); else { Node* splitTo = insertionPosition.containerNode(); if (splitTo->isTextNode() && insertionPosition.offsetInContainerNode() >= caretMaxOffset(splitTo)) splitTo = NodeTraversal::next(splitTo, startBlock.get()); ASSERT(splitTo); splitTreeToNode(splitTo, startBlock.get()); for (n = startBlock->firstChild(); n; n = n->nextSibling()) { VisiblePosition beforeNodePosition = positionBeforeNode(n); if (!beforeNodePosition.isNull() && comparePositions(VisiblePosition(insertionPosition), beforeNodePosition) <= 0) break; } } moveRemainingSiblingsToNewParent(n, blockToInsert.get(), blockToInsert); } // Handle whitespace that occurs after the split if (positionAfterSplit.isNotNull()) { document().updateLayoutIgnorePendingStylesheets(); if (!positionAfterSplit.isRenderedCharacter()) { // Clear out all whitespace and insert one non-breaking space ASSERT(!positionAfterSplit.containerNode()->renderer() || positionAfterSplit.containerNode()->renderer()->style()->collapseWhiteSpace()); deleteInsignificantTextDownstream(positionAfterSplit); if (positionAfterSplit.deprecatedNode()->isTextNode()) insertTextIntoNode(toText(positionAfterSplit.containerNode()), 0, nonBreakingSpaceString()); } } setEndingSelection(VisibleSelection(firstPositionInNode(blockToInsert.get()), DOWNSTREAM, endingSelection().isDirectional())); applyStyleAfterInsertion(startBlock.get()); }
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); } }