void visibleTextQuads(const Range& range, Vector<FloatQuad>& quads, bool useSelectionHeight) { // Range::textQuads includes hidden text, which we don't want. // To work around this, this is a copy of it which skips hidden elements. Node* startContainer = range.startContainer(); Node* endContainer = range.endContainer(); if (!startContainer || !endContainer) return; Node* stopNode = range.pastLastNode(); for (Node* node = range.firstNode(); node != stopNode; node = node->traverseNextNode()) { RenderObject* r = node->renderer(); if (!r || !r->isText()) continue; if (r->style()->visibility() != VISIBLE) continue; RenderText* renderText = toRenderText(r); int startOffset = node == startContainer ? range.startOffset() : 0; int endOffset = node == endContainer ? range.endOffset() : std::numeric_limits<int>::max(); renderText->absoluteQuadsForRange(quads, startOffset, endOffset, useSelectionHeight); } }
static bool shouldPreserveNewline(const Range& range) { if (Node* node = range.firstNode()) { if (RenderObject* renderer = node->renderer()) return renderer->style()->preserveNewline(); } if (Node* node = range.startPosition().anchorNode()) { if (RenderObject* renderer = node->renderer()) return renderer->style()->preserveNewline(); } return false; }
static bool hasNonInlineOrReplacedElements(const Range& range) { Node* stopNode = range.pastLastNode(); for (Node* node = range.firstNode(); node != stopNode; node = NodeTraversal::next(*node)) { if (!node) continue; RenderObject* renderer = node->renderer(); if (!renderer) continue; if ((!renderer->isInline() || renderer->isReplaced()) && range.intersectsNode(node, ASSERT_NO_EXCEPTION)) return true; } return false; }
PassRefPtr<DocumentFragment> createFragmentFromText(Range& context, const String& text) { Document& document = context.ownerDocument(); RefPtr<DocumentFragment> fragment = document.createDocumentFragment(); if (text.isEmpty()) return fragment.release(); String string = text; string.replace("\r\n", "\n"); string.replace('\r', '\n'); if (contextPreservesNewline(context)) { fragment->appendChild(document.createTextNode(string), ASSERT_NO_EXCEPTION); if (string.endsWith('\n')) { RefPtr<Element> element = createBreakElement(document); element->setAttribute(classAttr, AppleInterchangeNewline); fragment->appendChild(element.release(), ASSERT_NO_EXCEPTION); } return fragment.release(); } // A string with no newlines gets added inline, rather than being put into a paragraph. if (string.find('\n') == notFound) { fillContainerFromString(fragment.get(), string); return fragment.release(); } // Break string into paragraphs. Extra line breaks turn into empty paragraphs. Node* blockNode = enclosingBlock(context.firstNode()); Element* block = toElement(blockNode); bool useClonesOfEnclosingBlock = blockNode && blockNode->isElementNode() && !block->hasTagName(bodyTag) && !block->hasTagName(htmlTag) && block != editableRootForPosition(context.startPosition()); bool useLineBreak = enclosingTextFormControl(context.startPosition()); Vector<String> list; string.split('\n', true, list); // true gets us empty strings in the list size_t numLines = list.size(); for (size_t i = 0; i < numLines; ++i) { const String& s = list[i]; RefPtr<Element> element; if (s.isEmpty() && i + 1 == numLines) { // For last line, use the "magic BR" rather than a P. element = createBreakElement(document); element->setAttribute(classAttr, AppleInterchangeNewline); } else if (useLineBreak) { element = createBreakElement(document); fillContainerFromString(fragment.get(), s); } else { if (useClonesOfEnclosingBlock) element = block->cloneElementWithoutChildren(); else element = createDefaultParagraphElement(document); fillContainerFromString(element.get(), s); } fragment->appendChild(element.release(), ASSERT_NO_EXCEPTION); } return fragment.release(); }
// 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(); }