DragImageRef createDragImageForRange(Frame& frame, Range& range, bool forceBlackText) { frame.document()->updateLayout(); RenderView* view = frame.contentRenderer(); if (!view) return nullptr; // To snapshot the range, temporarily select it and take selection snapshot. Position start = range.startPosition(); Position candidate = start.downstream(); if (candidate.deprecatedNode() && candidate.deprecatedNode()->renderer()) start = candidate; Position end = range.endPosition(); candidate = end.upstream(); if (candidate.deprecatedNode() && candidate.deprecatedNode()->renderer()) end = candidate; if (start.isNull() || end.isNull() || start == end) return nullptr; const ScopedFrameSelectionState selectionState(frame); RenderObject* startRenderer = start.deprecatedNode()->renderer(); RenderObject* endRenderer = end.deprecatedNode()->renderer(); if (!startRenderer || !endRenderer) return nullptr; SnapshotOptions options = forceBlackText ? SnapshotOptionsForceBlackText : SnapshotOptionsNone; view->setSelection(startRenderer, start.deprecatedEditingOffset(), endRenderer, end.deprecatedEditingOffset(), RenderView::RepaintNothing); std::unique_ptr<ImageBuffer> snapshot = snapshotSelection(frame, options); return createDragImageFromSnapshot(std::move(snapshot), nullptr); }
VisibleSelection::VisibleSelection(const Range& range, EAffinity affinity, bool isDirectional) : m_base(range.startPosition()) , m_extent(range.endPosition()) , m_affinity(affinity) , m_isDirectional(isDirectional) { validate(); }
int HTMLTextFormControlElement::indexForVisiblePosition(const VisiblePosition& pos) const { Position indexPosition = pos.deepEquivalent().parentAnchoredEquivalent(); if (enclosingTextFormControl(indexPosition) != this) return 0; ASSERT(indexPosition.document()); Range* range = Range::create(*indexPosition.document()); range->setStart(innerEditorElement(), 0, ASSERT_NO_EXCEPTION); range->setEnd(indexPosition.computeContainerNode(), indexPosition.offsetInContainerNode(), ASSERT_NO_EXCEPTION); return TextIterator::rangeLength(range->startPosition(), range->endPosition()); }
void DOMSelection::addRange(Range& range) { if (!m_frame) return; FrameSelection& selection = m_frame->selection(); if (selection.isNone()) { selection.moveTo(&range); return; } RefPtr<Range> normalizedRange = selection.selection().toNormalizedRange(); if (!normalizedRange) return; if (range.compareBoundaryPoints(Range::START_TO_START, *normalizedRange, IGNORE_EXCEPTION) == -1) { // We don't support discontiguous selection. We don't do anything if r and range don't intersect. if (range.compareBoundaryPoints(Range::START_TO_END, *normalizedRange, IGNORE_EXCEPTION) > -1) { if (range.compareBoundaryPoints(Range::END_TO_END, *normalizedRange, IGNORE_EXCEPTION) == -1) { // The original range and r intersect. selection.moveTo(range.startPosition(), normalizedRange->endPosition(), DOWNSTREAM); } else { // r contains the original range. selection.moveTo(&range); } } } else { // We don't support discontiguous selection. We don't do anything if r and range don't intersect. ExceptionCode ec = 0; if (range.compareBoundaryPoints(Range::END_TO_START, *normalizedRange, ec) < 1 && !ec) { if (range.compareBoundaryPoints(Range::END_TO_END, *normalizedRange, IGNORE_EXCEPTION) == -1) { // The original range contains r. selection.moveTo(normalizedRange.get()); } else { // The original range and r intersect. selection.moveTo(normalizedRange->startPosition(), range.endPosition(), DOWNSTREAM); } } } }
void DOMSelection::addRange(Range* newRange) { DCHECK(newRange); if (!isAvailable()) return; if (newRange->ownerDocument() != frame()->document()) return; if (!newRange->isConnected()) { addConsoleError("The given range isn't in document."); return; } FrameSelection& selection = frame()->selection(); if (newRange->ownerDocument() != selection.document()) { // "editing/selection/selection-in-iframe-removed-crash.html" goes here. return; } // TODO(xiaochengh): The use of updateStyleAndLayoutIgnorePendingStylesheets // needs to be audited. See http://crbug.com/590369 for more details. // In the long term, we should change FrameSelection::setSelection to take a // parameter that does not require clean layout, so that modifying selection // no longer performs synchronous layout by itself. frame()->document()->updateStyleAndLayoutIgnorePendingStylesheets(); if (selection.isNone()) { selection.setSelectedRange(EphemeralRange(newRange), VP_DEFAULT_AFFINITY); return; } Range* originalRange = selection.firstRange(); if (originalRange->startContainer()->document() != newRange->startContainer()->document()) { addConsoleError( "The given range does not belong to the current selection's document."); return; } if (originalRange->startContainer()->treeScope() != newRange->startContainer()->treeScope()) { addConsoleError( "The given range and the current selection belong to two different " "document fragments."); return; } if (originalRange->compareBoundaryPoints(Range::kStartToEnd, newRange, ASSERT_NO_EXCEPTION) < 0 || newRange->compareBoundaryPoints(Range::kStartToEnd, originalRange, ASSERT_NO_EXCEPTION) < 0) { addConsoleError("Discontiguous selection is not supported."); return; } // FIXME: "Merge the ranges if they intersect" is Blink-specific behavior; // other browsers supporting discontiguous selection (obviously) keep each // Range added and return it in getRangeAt(). But it's unclear if we can // really do the same, since we don't support discontiguous selection. Further // discussions at // <https://code.google.com/p/chromium/issues/detail?id=353069>. Range* start = originalRange->compareBoundaryPoints( Range::kStartToStart, newRange, ASSERT_NO_EXCEPTION) < 0 ? originalRange : newRange; Range* end = originalRange->compareBoundaryPoints(Range::kEndToEnd, newRange, ASSERT_NO_EXCEPTION) < 0 ? newRange : originalRange; const EphemeralRange merged = EphemeralRange(start->startPosition(), end->endPosition()); TextAffinity affinity = selection.selection().affinity(); selection.setSelectedRange(merged, affinity); }
void InsertListCommand::doApplyForSingleParagraph(bool forceCreateList, const HTMLQualifiedName& listTag, Range& currentSelection) { // FIXME: This will produce unexpected results for a selection that starts just before a // table and ends inside the first cell, selectionForParagraphIteration should probably // be renamed and deployed inside setEndingSelection(). Node* selectionNode = endingSelection().start().deprecatedNode(); Node* listChildNode = enclosingListChild(selectionNode); bool switchListType = false; if (listChildNode) { // Remove the list chlild. RefPtrWillBeRawPtr<HTMLElement> listElement = enclosingList(listChildNode); if (!listElement) { listElement = fixOrphanedListChild(listChildNode); listElement = mergeWithNeighboringLists(listElement); } if (!listElement->hasTagName(listTag)) // listChildNode will be removed from the list and a list of type m_type will be created. switchListType = true; // If the list is of the desired type, and we are not removing the list, then exit early. if (!switchListType && forceCreateList) return; // If the entire list is selected, then convert the whole list. if (switchListType && isNodeVisiblyContainedWithin(*listElement, currentSelection)) { bool rangeStartIsInList = visiblePositionBeforeNode(*listElement) == VisiblePosition(currentSelection.startPosition()); bool rangeEndIsInList = visiblePositionAfterNode(*listElement) == VisiblePosition(currentSelection.endPosition()); RefPtrWillBeRawPtr<HTMLElement> newList = createHTMLElement(document(), listTag); insertNodeBefore(newList, listElement); Node* firstChildInList = enclosingListChild(VisiblePosition(firstPositionInNode(listElement.get())).deepEquivalent().deprecatedNode(), listElement.get()); Element* outerBlock = firstChildInList && isBlockFlowElement(*firstChildInList) ? toElement(firstChildInList) : listElement.get(); moveParagraphWithClones(VisiblePosition(firstPositionInNode(listElement.get())), VisiblePosition(lastPositionInNode(listElement.get())), newList.get(), outerBlock); // Manually remove listNode because moveParagraphWithClones sometimes leaves it behind in the document. // See the bug 33668 and editing/execCommand/insert-list-orphaned-item-with-nested-lists.html. // FIXME: This might be a bug in moveParagraphWithClones or deleteSelection. if (listElement && listElement->inDocument()) removeNode(listElement); newList = mergeWithNeighboringLists(newList); // Restore the start and the end of current selection if they started inside listNode // because moveParagraphWithClones could have removed them. if (rangeStartIsInList && newList) currentSelection.setStart(newList, 0, IGNORE_EXCEPTION); if (rangeEndIsInList && newList) currentSelection.setEnd(newList, lastOffsetInNode(newList.get()), IGNORE_EXCEPTION); setEndingSelection(VisiblePosition(firstPositionInNode(newList.get()))); return; } unlistifyParagraph(endingSelection().visibleStart(), listElement.get(), listChildNode); } if (!listChildNode || switchListType || forceCreateList) m_listElement = listifyParagraph(endingSelection().visibleStart(), listTag); }
SurroundingText::SurroundingText(const Range& range, unsigned maxLength) : m_startOffsetInContent(0) , m_endOffsetInContent(0) { initialize(range.startPosition(), range.endPosition(), maxLength); }
bool InsertListCommand::doApplyForSingleParagraph( bool forceCreateList, const HTMLQualifiedName& listTag, Range& currentSelection, EditingState* editingState) { // FIXME: This will produce unexpected results for a selection that starts // just before a table and ends inside the first cell, // selectionForParagraphIteration should probably be renamed and deployed // inside setEndingSelection(). Node* selectionNode = endingSelection().start().anchorNode(); Node* listChildNode = enclosingListChild(selectionNode); bool switchListType = false; if (listChildNode) { if (!hasEditableStyle(*listChildNode->parentNode())) return false; // Remove the list child. HTMLElement* listElement = enclosingList(listChildNode); if (listElement) { if (!hasEditableStyle(*listElement)) { // Since, |listElement| is uneditable, we can't move |listChild| // out from |listElement|. return false; } if (!hasEditableStyle(*listElement->parentNode())) { // Since parent of |listElement| is uneditable, we can not remove // |listElement| for switching list type neither unlistify. return false; } } if (!listElement) { listElement = fixOrphanedListChild(listChildNode, editingState); if (editingState->isAborted()) return false; listElement = mergeWithNeighboringLists(listElement, editingState); if (editingState->isAborted()) return false; document().updateStyleAndLayoutIgnorePendingStylesheets(); } DCHECK(hasEditableStyle(*listElement)); DCHECK(hasEditableStyle(*listElement->parentNode())); if (!listElement->hasTagName(listTag)) { // |listChildNode| will be removed from the list and a list of type // |m_type| will be created. switchListType = true; } // If the list is of the desired type, and we are not removing the list, // then exit early. if (!switchListType && forceCreateList) return true; // If the entire list is selected, then convert the whole list. if (switchListType && isNodeVisiblyContainedWithin(*listElement, currentSelection)) { bool rangeStartIsInList = visiblePositionBeforeNode(*listElement).deepEquivalent() == createVisiblePosition(currentSelection.startPosition()) .deepEquivalent(); bool rangeEndIsInList = visiblePositionAfterNode(*listElement).deepEquivalent() == createVisiblePosition(currentSelection.endPosition()) .deepEquivalent(); HTMLElement* newList = createHTMLElement(document(), listTag); insertNodeBefore(newList, listElement, editingState); if (editingState->isAborted()) return false; document().updateStyleAndLayoutIgnorePendingStylesheets(); Node* firstChildInList = enclosingListChild(VisiblePosition::firstPositionInNode(listElement) .deepEquivalent() .anchorNode(), listElement); Element* outerBlock = firstChildInList && isBlockFlowElement(*firstChildInList) ? toElement(firstChildInList) : listElement; moveParagraphWithClones(VisiblePosition::firstPositionInNode(listElement), VisiblePosition::lastPositionInNode(listElement), newList, outerBlock, editingState); if (editingState->isAborted()) return false; // Manually remove listNode because moveParagraphWithClones sometimes // leaves it behind in the document. See the bug 33668 and // editing/execCommand/insert-list-orphaned-item-with-nested-lists.html. // FIXME: This might be a bug in moveParagraphWithClones or // deleteSelection. if (listElement && listElement->isConnected()) { removeNode(listElement, editingState); if (editingState->isAborted()) return false; } newList = mergeWithNeighboringLists(newList, editingState); if (editingState->isAborted()) return false; // Restore the start and the end of current selection if they started // inside listNode because moveParagraphWithClones could have removed // them. if (rangeStartIsInList && newList) currentSelection.setStart(newList, 0, IGNORE_EXCEPTION); if (rangeEndIsInList && newList) currentSelection.setEnd(newList, Position::lastOffsetInNode(newList), IGNORE_EXCEPTION); document().updateStyleAndLayoutIgnorePendingStylesheets(); setEndingSelection(VisiblePosition::firstPositionInNode(newList)); return true; } unlistifyParagraph(endingSelection().visibleStart(), listElement, listChildNode, editingState); if (editingState->isAborted()) return false; document().updateStyleAndLayoutIgnorePendingStylesheets(); } if (!listChildNode || switchListType || forceCreateList) listifyParagraph(endingSelection().visibleStart(), listTag, editingState); return true; }
// FIXME: Shouldn't we omit style info when annotate == DoNotAnnotateForInterchange? // FIXME: At least, annotation and style info should probably not be included in range.markupString() static String createMarkupInternal(Document& document, const Range& range, const Range& updatedRange, Vector<Node*>* nodes, EAnnotateForInterchange shouldAnnotate, bool convertBlocksToInlines, EAbsoluteURLs shouldResolveURLs) { DEFINE_STATIC_LOCAL(const String, interchangeNewlineString, (ASCIILiteral("<br class=\"" AppleInterchangeNewline "\">"))); bool collapsed = updatedRange.collapsed(ASSERT_NO_EXCEPTION); if (collapsed) return emptyString(); Node* commonAncestor = updatedRange.commonAncestorContainer(ASSERT_NO_EXCEPTION); if (!commonAncestor) return emptyString(); document.updateLayoutIgnorePendingStylesheets(); Node* body = enclosingNodeWithTag(firstPositionInNode(commonAncestor), bodyTag); Node* fullySelectedRoot = 0; // FIXME: Do this for all fully selected blocks, not just the body. if (body && areRangesEqual(VisibleSelection::selectionFromContentsOfNode(body).toNormalizedRange().get(), &range)) fullySelectedRoot = body; Node* specialCommonAncestor = highestAncestorToWrapMarkup(&updatedRange, shouldAnnotate); StyledMarkupAccumulator accumulator(nodes, shouldResolveURLs, shouldAnnotate, &updatedRange, specialCommonAncestor); Node* pastEnd = updatedRange.pastLastNode(); Node* startNode = updatedRange.firstNode(); VisiblePosition visibleStart(updatedRange.startPosition(), VP_DEFAULT_AFFINITY); VisiblePosition visibleEnd(updatedRange.endPosition(), VP_DEFAULT_AFFINITY); if (shouldAnnotate == AnnotateForInterchange && needInterchangeNewlineAfter(visibleStart)) { if (visibleStart == visibleEnd.previous()) return interchangeNewlineString; accumulator.appendString(interchangeNewlineString); startNode = visibleStart.next().deepEquivalent().deprecatedNode(); if (pastEnd && Range::compareBoundaryPoints(startNode, 0, pastEnd, 0, ASSERT_NO_EXCEPTION) >= 0) return interchangeNewlineString; } Node* lastClosed = accumulator.serializeNodes(startNode, pastEnd); if (specialCommonAncestor && lastClosed) { // Also include all of the ancestors of lastClosed up to this special ancestor. for (ContainerNode* ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) { if (ancestor == fullySelectedRoot && !convertBlocksToInlines) { RefPtr<EditingStyle> fullySelectedRootStyle = styleFromMatchedRulesAndInlineDecl(fullySelectedRoot); // Bring the background attribute over, but not as an attribute because a background attribute on a div // appears to have no effect. if ((!fullySelectedRootStyle || !fullySelectedRootStyle->style() || !fullySelectedRootStyle->style()->getPropertyCSSValue(CSSPropertyBackgroundImage)) && toElement(fullySelectedRoot)->hasAttribute(backgroundAttr)) fullySelectedRootStyle->style()->setProperty(CSSPropertyBackgroundImage, "url('" + toElement(fullySelectedRoot)->getAttribute(backgroundAttr) + "')"); if (fullySelectedRootStyle->style()) { // Reset the CSS properties to avoid an assertion error in addStyleMarkup(). // This assertion is caused at least when we select all text of a <body> element whose // 'text-decoration' property is "inherit", and copy it. if (!propertyMissingOrEqualToNone(fullySelectedRootStyle->style(), CSSPropertyTextDecoration)) fullySelectedRootStyle->style()->setProperty(CSSPropertyTextDecoration, CSSValueNone); if (!propertyMissingOrEqualToNone(fullySelectedRootStyle->style(), CSSPropertyWebkitTextDecorationsInEffect)) fullySelectedRootStyle->style()->setProperty(CSSPropertyWebkitTextDecorationsInEffect, CSSValueNone); accumulator.wrapWithStyleNode(fullySelectedRootStyle->style(), document, true); } } else { // Since this node and all the other ancestors are not in the selection we want to set RangeFullySelectsNode to DoesNotFullySelectNode // so that styles that affect the exterior of the node are not included. accumulator.wrapWithNode(*ancestor, convertBlocksToInlines, StyledMarkupAccumulator::DoesNotFullySelectNode); } if (nodes) nodes->append(ancestor); lastClosed = ancestor; if (ancestor == specialCommonAncestor) break; } } // FIXME: The interchange newline should be placed in the block that it's in, not after all of the content, unconditionally. if (shouldAnnotate == AnnotateForInterchange && needInterchangeNewlineAfter(visibleEnd.previous())) accumulator.appendString(interchangeNewlineString); return accumulator.takeResults(); }