Beispiel #1
0
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();
}
Beispiel #3
0
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);
            }
        }
    }
}
Beispiel #5
0
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);
}
Beispiel #7
0
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;
}
Beispiel #9
0
// 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();
}