diff options
Diffstat (limited to 'WebCore/editing')
37 files changed, 1385 insertions, 903 deletions
diff --git a/WebCore/editing/ApplyBlockElementCommand.cpp b/WebCore/editing/ApplyBlockElementCommand.cpp new file mode 100644 index 0000000..ecd3d9b --- /dev/null +++ b/WebCore/editing/ApplyBlockElementCommand.cpp @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2006, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ApplyBlockElementCommand.h" + +#include "HTMLElement.h" +#include "HTMLNames.h" +#include "Text.h" +#include "TextIterator.h" +#include "VisiblePosition.h" +#include "htmlediting.h" +#include "visible_units.h" + +namespace WebCore { + +using namespace HTMLNames; + +ApplyBlockElementCommand::ApplyBlockElementCommand(Document* document, const QualifiedName& tagName, const AtomicString& className, const AtomicString& inlineStyle) + : CompositeEditCommand(document) + , m_tagName(tagName) + , m_className(className) + , m_inlineStyle(inlineStyle) +{ +} + +ApplyBlockElementCommand::ApplyBlockElementCommand(Document* document, const QualifiedName& tagName) + : CompositeEditCommand(document) + , m_tagName(tagName) +{ +} + +void ApplyBlockElementCommand::doApply() +{ + if (!endingSelection().isNonOrphanedCaretOrRange()) + return; + + if (!endingSelection().rootEditableElement()) + return; + + VisiblePosition visibleEnd = endingSelection().visibleEnd(); + VisiblePosition visibleStart = endingSelection().visibleStart(); + // When a selection ends at the start of a paragraph, we rarely paint + // the selection gap before that paragraph, because there often is no gap. + // In a case like this, it's not obvious to the user that the selection + // ends "inside" that paragraph, so it would be confusing if Indent/Outdent + // operated on that paragraph. + // FIXME: We paint the gap before some paragraphs that are indented with left + // margin/padding, but not others. We should make the gap painting more consistent and + // then use a left margin/padding rule here. + if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd)) + setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(true))); + + VisibleSelection selection = selectionForParagraphIteration(endingSelection()); + VisiblePosition startOfSelection = selection.visibleStart(); + VisiblePosition endOfSelection = selection.visibleEnd(); + ASSERT(!startOfSelection.isNull()); + ASSERT(!endOfSelection.isNull()); + int startIndex = indexForVisiblePosition(startOfSelection); + int endIndex = indexForVisiblePosition(endOfSelection); + + formatSelection(startOfSelection, endOfSelection); + + updateLayout(); + + RefPtr<Range> startRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), startIndex, 0, true); + RefPtr<Range> endRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), endIndex, 0, true); + if (startRange && endRange) + setEndingSelection(VisibleSelection(startRange->startPosition(), endRange->startPosition(), DOWNSTREAM)); +} + +void ApplyBlockElementCommand::formatSelection(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection) +{ + // Special case empty unsplittable elements because there's nothing to split + // and there's nothing to move. + Position start = startOfSelection.deepEquivalent().downstream(); + if (isAtUnsplittableElement(start)) { + RefPtr<Element> blockquote = createBlockElement(); + insertNodeAt(blockquote, start); + RefPtr<Element> placeholder = createBreakElement(document()); + appendNode(placeholder, blockquote); + setEndingSelection(VisibleSelection(Position(placeholder.get(), 0), DOWNSTREAM)); + return; + } + + RefPtr<Element> blockquoteForNextIndent; + VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection); + VisiblePosition endAfterSelection = endOfParagraph(endOfParagraph(endOfSelection).next()); + VisiblePosition endOfLastParagraph = endOfParagraph(endOfSelection); + + bool atEnd = false; + Position end; + while (endOfCurrentParagraph != endAfterSelection && !atEnd) { + if (endOfCurrentParagraph == endOfLastParagraph) + atEnd = true; + + rangeForParagraphSplittingTextNodesIfNeeded(endOfCurrentParagraph, start, end); + endOfCurrentParagraph = end; + + Position afterEnd = end.next(); + Node* enclosingCell = enclosingNodeOfType(start, &isTableCell); + VisiblePosition endOfNextParagraph = endOfNextParagrahSplittingTextNodesIfNeeded(endOfCurrentParagraph, start, end); + + formatRange(start, end, blockquoteForNextIndent); + + // Don't put the next paragraph in the blockquote we just created for this paragraph unless + // the next paragraph is in the same cell. + if (enclosingCell && enclosingCell != enclosingNodeOfType(endOfNextParagraph.deepEquivalent(), &isTableCell)) + blockquoteForNextIndent = 0; + + // indentIntoBlockquote could move more than one paragraph if the paragraph + // is in a list item or a table. As a result, endAfterSelection could refer to a position + // no longer in the document. + if (endAfterSelection.isNotNull() && !endAfterSelection.deepEquivalent().node()->inDocument()) + break; + // Sanity check: Make sure our moveParagraph calls didn't remove endOfNextParagraph.deepEquivalent().node() + // If somehow we did, return to prevent crashes. + if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent().node()->inDocument()) { + ASSERT_NOT_REACHED(); + return; + } + endOfCurrentParagraph = endOfNextParagraph; + } +} + +static bool isNewLineAtPosition(const Position& position) +{ + if (position.anchorType() != Position::PositionIsOffsetInAnchor) + return false; + + Node* textNode = position.containerNode(); + int offset = position.offsetInContainerNode(); + if (!textNode || !textNode->isTextNode() || offset < 0 || offset >= textNode->maxCharacterOffset()) + return false; + + ExceptionCode ec = 0; + String textAtPosition = static_cast<Text*>(textNode)->substringData(offset, 1, ec); + if (ec) + return false; + + return textAtPosition[0] == '\n'; +} + +static RenderStyle* renderStyleOfEnclosingTextNode(const Position& position) +{ + if (position.anchorType() != Position::PositionIsOffsetInAnchor + || !position.containerNode() + || !position.containerNode()->isTextNode() + || !position.containerNode()->renderer()) + return 0; + return position.containerNode()->renderer()->style(); +} + +void ApplyBlockElementCommand::rangeForParagraphSplittingTextNodesIfNeeded(const VisiblePosition& endOfCurrentParagraph, Position& start, Position& end) +{ + start = startOfParagraph(endOfCurrentParagraph).deepEquivalent(); + end = endOfCurrentParagraph.deepEquivalent(); + + RenderStyle* startStyle = renderStyleOfEnclosingTextNode(start); + if (startStyle) { + // Avoid obtanining the start of next paragraph for start + if (startStyle->preserveNewline() && isNewLineAtPosition(start) && !isNewLineAtPosition(start.previous()) && start.offsetInContainerNode() > 0) + start = startOfParagraph(end.previous()).deepEquivalent(); + + // If start is in the middle of a text node, split. + if (!startStyle->collapseWhiteSpace() && start.offsetInContainerNode() > 0) { + int startOffset = start.offsetInContainerNode(); + splitTextNode(static_cast<Text*>(start.node()), startOffset); + start = positionBeforeNode(start.node()); + if (start.node() == end.node()) { + ASSERT(end.offsetInContainerNode() >= startOffset); + end = Position(end.node(), end.offsetInContainerNode() - startOffset, Position::PositionIsOffsetInAnchor); + } + } + } + + RenderStyle* endStyle = renderStyleOfEnclosingTextNode(end); + if (endStyle) { + // Include \n at the end of line if we're at an empty paragraph + if (endStyle->preserveNewline() && start == end + && end.offsetInContainerNode() < end.containerNode()->maxCharacterOffset()) { + int endOffset = end.offsetInContainerNode(); + if (!isNewLineAtPosition(end.previous()) && isNewLineAtPosition(end)) + end = Position(end.node(), endOffset + 1, Position::PositionIsOffsetInAnchor); + } + + // If end is in the middle of a text node, split. + if (!endStyle->collapseWhiteSpace() && end.offsetInContainerNode() + && end.offsetInContainerNode() < end.containerNode()->maxCharacterOffset()) { + splitTextNode(static_cast<Text*>(end.node()), end.offsetInContainerNode()); + if (start.node() == end.node()) + start = positionBeforeNode(end.node()->previousSibling()); + end = lastPositionInNode(end.node()->previousSibling()); + } + } +} + +VisiblePosition ApplyBlockElementCommand::endOfNextParagrahSplittingTextNodesIfNeeded(VisiblePosition& endOfCurrentParagraph, Position& start, Position& end) +{ + VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next()); + Position position = endOfNextParagraph.deepEquivalent(); + RenderStyle* style = renderStyleOfEnclosingTextNode(position); + if (!style) + return endOfNextParagraph; + + RefPtr<Node> containerNode = position.containerNode(); + if (!style->preserveNewline() || !position.offsetInContainerNode() + || !isNewLineAtPosition(Position(containerNode.get(), 0, Position::PositionIsOffsetInAnchor))) + return endOfNextParagraph; + + // \n at the beginning of the text node immediately following the current paragraph is trimmed by moveParagraphWithClones. + // If endOfNextParagraph was pointing at this same text node, endOfNextParagraph will be shifted by one paragraph. + // Avoid this by splitting "\n" + splitTextNode(static_cast<Text*>(containerNode.get()), 1); + + if (start.anchorType() == Position::PositionIsOffsetInAnchor && containerNode.get() == start.containerNode()) { + ASSERT(start.offsetInContainerNode() < position.offsetInContainerNode()); + start = Position(containerNode->previousSibling(), start.offsetInContainerNode(), Position::PositionIsOffsetInAnchor); + } + if (end.anchorType() == Position::PositionIsOffsetInAnchor && containerNode.get() == end.containerNode()) { + ASSERT(end.offsetInContainerNode() < position.offsetInContainerNode()); + end = Position(containerNode->previousSibling(), end.offsetInContainerNode(), Position::PositionIsOffsetInAnchor); + } + + return Position(containerNode.get(), position.offsetInContainerNode() - 1, Position::PositionIsOffsetInAnchor); +} + +PassRefPtr<Element> ApplyBlockElementCommand::createBlockElement() const +{ + RefPtr<Element> element = createHTMLElement(document(), m_tagName); + if (m_className.length()) + element->setAttribute(classAttr, m_className); + if (m_inlineStyle.length()) + element->setAttribute(styleAttr, m_inlineStyle); + return element.release(); +} + +} diff --git a/WebCore/editing/ApplyBlockElementCommand.h b/WebCore/editing/ApplyBlockElementCommand.h new file mode 100644 index 0000000..1fc4931 --- /dev/null +++ b/WebCore/editing/ApplyBlockElementCommand.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ApplyBlockElementCommand_h +#define ApplyBlockElementCommand_h + +#include "CompositeEditCommand.h" + +namespace WebCore { + +class ApplyBlockElementCommand : public CompositeEditCommand { +protected: + ApplyBlockElementCommand(Document*, const QualifiedName& tagName, const AtomicString& className, const AtomicString& inlineStyle); + ApplyBlockElementCommand(Document*, const QualifiedName& tagName); + + virtual void formatSelection(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection); + PassRefPtr<Element> createBlockElement() const; + const QualifiedName tagName() const { return m_tagName; } + +private: + virtual void doApply(); + virtual void formatRange(const Position& start, const Position&, RefPtr<Element>&) = 0; + void rangeForParagraphSplittingTextNodesIfNeeded(const VisiblePosition&, Position&, Position&); + VisiblePosition endOfNextParagrahSplittingTextNodesIfNeeded(VisiblePosition&, Position&, Position&); + + QualifiedName m_tagName; + AtomicString m_className; + AtomicString m_inlineStyle; +}; + +} + +#endif diff --git a/WebCore/editing/ApplyStyleCommand.cpp b/WebCore/editing/ApplyStyleCommand.cpp index 3ff7169..3f60a8b 100644 --- a/WebCore/editing/ApplyStyleCommand.cpp +++ b/WebCore/editing/ApplyStyleCommand.cpp @@ -395,6 +395,8 @@ static int getTextAlignment(CSSStyleDeclaration* style) case CSSValueCenter: case CSSValueWebkitCenter: return CSSValueCenter; + case CSSValueJustify: + return CSSValueJustify; case CSSValueLeft: case CSSValueWebkitLeft: return CSSValueLeft; @@ -638,9 +640,13 @@ void ApplyStyleCommand::applyBlockStyle(CSSMutableStyleDeclaration *style) start = end; end = swap; } - + VisiblePosition visibleStart(start); VisiblePosition visibleEnd(end); + + if (visibleStart.isNull() || visibleStart.isOrphan() || visibleEnd.isNull() || visibleEnd.isOrphan()) + return; + // Save and restore the selection endpoints using their indices in the document, since // addBlockStyleIfNeeded may moveParagraphs, which can remove these endpoints. // Calculate start and end indices from the start of the tree that they're in. @@ -1093,6 +1099,14 @@ void ApplyStyleCommand::fixRangeAndApplyInlineStyle(CSSMutableStyleDeclaration* if (start == end && start.node()->hasTagName(brTag)) pastEndNode = start.node()->traverseNextNode(); + // Start from the highest fully selected ancestor so that we can modify the fully selected node. + // e.g. When applying font-size: large on <font color="blue">hello</font>, we need to include the font element in our run + // to generate <font color="blue" size="4">hello</font> instead of <font color="blue"><font size="4">hello</font></font> + RefPtr<Range> range = Range::create(startNode->document(), start, end); + Element* editableRoot = startNode->rootEditableElement(); + while (editableRoot && startNode->parentNode() != editableRoot && isNodeVisiblyContainedWithin(startNode->parentNode(), range.get())) + startNode = startNode->parentNode(); + applyInlineStyleToNodeRange(style, startNode, pastEndNode); } @@ -1163,7 +1177,21 @@ void ApplyStyleCommand::applyInlineStyleToNodeRange(CSSMutableStyleDeclaration* bool ApplyStyleCommand::removeStyleFromRunBeforeApplyingStyle(CSSMutableStyleDeclaration* style, Node*& runStart, Node*& runEnd) { + ASSERT(runStart && runEnd && runStart->parentNode() == runEnd->parentNode()); Node* pastEndNode = runEnd->traverseNextSibling(); + bool needToApplyStyle = false; + for (Node* node = runStart; node && node != pastEndNode; node = node->traverseNextNode()) { + if (node->childNodeCount()) + continue; + if (getPropertiesNotIn(style, computedStyle(node).get())->length() + || (m_styledInlineElement && !enclosingNodeWithTag(positionBeforeNode(node), m_styledInlineElement->tagQName()))) { + needToApplyStyle = true; + break; + } + } + if (!needToApplyStyle) + return false; + Node* next; for (Node* node = runStart; node && node != pastEndNode; node = next) { next = node->traverseNextNode(); @@ -1172,7 +1200,7 @@ bool ApplyStyleCommand::removeStyleFromRunBeforeApplyingStyle(CSSMutableStyleDec Node* previousSibling = node->previousSibling(); Node* nextSibling = node->nextSibling(); - Node* parent = node->parentNode(); + ContainerNode* parent = node->parentNode(); removeInlineStyleFromElement(style, static_cast<HTMLElement*>(node), RemoveAlways); if (!node->inDocument()) { // FIXME: We might need to update the start and the end of current selection here but need a test. @@ -1191,12 +1219,16 @@ bool ApplyStyleCommand::removeInlineStyleFromElement(CSSMutableStyleDeclaration* ASSERT(style); ASSERT(element); + if (!element->parentNode() || !element->parentNode()->isContentEditable()) + return false; + if (m_styledInlineElement && element->hasTagName(m_styledInlineElement->tagQName())) { - if (mode != RemoveNone) { - if (extractedStyle && element->inlineStyleDecl()) - extractedStyle->merge(element->inlineStyleDecl()); - removeNodePreservingChildren(element); - } + if (mode == RemoveNone) + return true; + ASSERT(extractedStyle); + if (element->inlineStyleDecl()) + extractedStyle->merge(element->inlineStyleDecl()); + removeNodePreservingChildren(element); return true; } @@ -1335,7 +1367,7 @@ void ApplyStyleCommand::replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement*& if (removeNode) removeNodePreservingChildren(elem); else { - HTMLElement* newSpanElement = replaceNodeWithSpanPreservingChildrenAndAttributes(elem); + HTMLElement* newSpanElement = replaceElementWithSpanPreservingChildrenAndAttributes(elem); ASSERT(newSpanElement && newSpanElement->inDocument()); elem = newSpanElement; } @@ -1555,12 +1587,19 @@ void ApplyStyleCommand::removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration> Node* node = start.node(); while (node) { - Node* next = node->traverseNextNode(); + RefPtr<Node> next = node->traverseNextNode(); if (node->isHTMLElement() && nodeFullySelected(node, start, end)) { - HTMLElement* elem = static_cast<HTMLElement*>(node); - Node* prev = elem->traversePreviousNodePostOrder(); - Node* next = elem->traverseNextNode(); - removeInlineStyleFromElement(style.get(), elem); + RefPtr<HTMLElement> elem = static_cast<HTMLElement*>(node); + RefPtr<Node> prev = elem->traversePreviousNodePostOrder(); + RefPtr<Node> next = elem->traverseNextNode(); + RefPtr<CSSMutableStyleDeclaration> styleToPushDown; + PassRefPtr<Node> childNode = 0; + if (m_styledInlineElement && elem->hasTagName(m_styledInlineElement->tagQName())) { + styleToPushDown = CSSMutableStyleDeclaration::create(); + childNode = elem->firstChild(); + } + + removeInlineStyleFromElement(style.get(), elem.get(), RemoveIfNeeded, styleToPushDown.get()); if (!elem->inDocument()) { if (s.node() == elem) { // Since elem must have been fully selected, and it is at the start @@ -1573,13 +1612,18 @@ void ApplyStyleCommand::removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration> // of the selection, it is clear we can set the new e offset to // the max range offset of prev. ASSERT(e.deprecatedEditingOffset() >= lastOffsetForEditing(e.node())); - e = Position(prev, lastOffsetForEditing(prev)); + e = Position(prev, lastOffsetForEditing(prev.get())); } } + + if (styleToPushDown) { + for (; childNode; childNode = childNode->nextSibling()) + applyInlineStyleToPushDown(childNode.get(), styleToPushDown.get()); + } } if (node == end.node()) break; - node = next; + node = next.get(); } ASSERT(s.node()->inDocument()); @@ -1650,7 +1694,7 @@ void ApplyStyleCommand::splitTextElementAtEnd(const Position& start, const Posit bool ApplyStyleCommand::shouldSplitTextElement(Element* element, CSSMutableStyleDeclaration* style) { - if (!element || !element->isHTMLElement() || !element->parentElement() || !element->parentElement()->isContentEditable()) + if (!element || !element->isHTMLElement()) return false; return shouldRemoveInlineStyleFromElement(style, static_cast<HTMLElement*>(element)); @@ -1835,26 +1879,69 @@ void ApplyStyleCommand::addBlockStyle(const StyleChange& styleChange, HTMLElemen void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclaration *style, Node *startNode, Node *endNode, EAddStyledElement addStyledElement) { - StyleChange styleChange(style, Position(startNode, 0)); + // It's okay to obtain the style at the startNode because we've removed all relevant styles from the current run. + RefPtr<HTMLElement> dummyElement; + Position positionForStyleComparison; + if (!startNode->isElementNode()) { + dummyElement = createStyleSpanElement(document()); + insertNodeAt(dummyElement, positionBeforeNode(startNode)); + positionForStyleComparison = positionBeforeNode(dummyElement.get()); + } else + positionForStyleComparison = firstPositionInNode(startNode); + + StyleChange styleChange(style, positionForStyleComparison); + + if (dummyElement) + removeNode(dummyElement); + + // Find appropriate font and span elements top-down. + HTMLElement* fontContainer = 0; + HTMLElement* styleContainer = 0; + for (Node* container = startNode; container && startNode == endNode; container = container->firstChild()) { + if (container->isHTMLElement() && container->hasTagName(fontTag)) + fontContainer = static_cast<HTMLElement*>(container); + bool styleContainerIsNotSpan = !styleContainer || !styleContainer->hasTagName(spanTag); + if (container->isHTMLElement() && (container->hasTagName(spanTag) || (styleContainerIsNotSpan && container->childNodeCount()))) + styleContainer = static_cast<HTMLElement*>(container); + if (!container->firstChild()) + break; + startNode = container->firstChild(); + endNode = container->lastChild(); + } // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes. if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) { - RefPtr<Element> fontElement = createFontElement(document()); - - if (styleChange.applyFontColor()) - fontElement->setAttribute(colorAttr, styleChange.fontColor()); - if (styleChange.applyFontFace()) - fontElement->setAttribute(faceAttr, styleChange.fontFace()); - if (styleChange.applyFontSize()) - fontElement->setAttribute(sizeAttr, styleChange.fontSize()); - - surroundNodeRangeWithElement(startNode, endNode, fontElement.get()); + if (fontContainer) { + if (styleChange.applyFontColor()) + setNodeAttribute(fontContainer, colorAttr, styleChange.fontColor()); + if (styleChange.applyFontFace()) + setNodeAttribute(fontContainer, faceAttr, styleChange.fontFace()); + if (styleChange.applyFontSize()) + setNodeAttribute(fontContainer, sizeAttr, styleChange.fontSize()); + } else { + RefPtr<Element> fontElement = createFontElement(document()); + if (styleChange.applyFontColor()) + fontElement->setAttribute(colorAttr, styleChange.fontColor()); + if (styleChange.applyFontFace()) + fontElement->setAttribute(faceAttr, styleChange.fontFace()); + if (styleChange.applyFontSize()) + fontElement->setAttribute(sizeAttr, styleChange.fontSize()); + surroundNodeRangeWithElement(startNode, endNode, fontElement.get()); + } } if (styleChange.cssStyle().length()) { - RefPtr<Element> styleElement = createStyleSpanElement(document()); - styleElement->setAttribute(styleAttr, styleChange.cssStyle()); - surroundNodeRangeWithElement(startNode, endNode, styleElement.release()); + if (styleContainer) { + CSSMutableStyleDeclaration* existingStyle = static_cast<HTMLElement*>(styleContainer)->inlineStyleDecl(); + if (existingStyle) + setNodeAttribute(styleContainer, styleAttr, existingStyle->cssText() + styleChange.cssStyle()); + else + setNodeAttribute(styleContainer, styleAttr, styleChange.cssStyle()); + } else { + RefPtr<Element> styleElement = createStyleSpanElement(document()); + styleElement->setAttribute(styleAttr, styleChange.cssStyle()); + surroundNodeRangeWithElement(startNode, endNode, styleElement.release()); + } } if (styleChange.applyBold()) diff --git a/WebCore/editing/BreakBlockquoteCommand.cpp b/WebCore/editing/BreakBlockquoteCommand.cpp index 2da6047..63956e5 100644 --- a/WebCore/editing/BreakBlockquoteCommand.cpp +++ b/WebCore/editing/BreakBlockquoteCommand.cpp @@ -67,7 +67,7 @@ void BreakBlockquoteCommand::doApply() // Find the top-most blockquote from the start. Element* topBlockquote = 0; - for (Node *node = pos.node()->parentNode(); node; node = node->parentNode()) { + for (ContainerNode* node = pos.node()->parentNode(); node; node = node->parentNode()) { if (isMailBlockquote(node)) topBlockquote = static_cast<Element*>(node); } diff --git a/WebCore/editing/CompositeEditCommand.cpp b/WebCore/editing/CompositeEditCommand.cpp index 356a717..a00db36 100644 --- a/WebCore/editing/CompositeEditCommand.cpp +++ b/WebCore/editing/CompositeEditCommand.cpp @@ -77,6 +77,10 @@ CompositeEditCommand::CompositeEditCommand(Document *document) { } +CompositeEditCommand::~CompositeEditCommand() +{ +} + void CompositeEditCommand::doUnapply() { size_t size = m_commands.size(); @@ -212,12 +216,12 @@ void CompositeEditCommand::removeNodePreservingChildren(PassRefPtr<Node> node) void CompositeEditCommand::removeNodeAndPruneAncestors(PassRefPtr<Node> node) { - RefPtr<Node> parent = node->parentNode(); + RefPtr<ContainerNode> parent = node->parentNode(); removeNode(node); prune(parent.release()); } -HTMLElement* CompositeEditCommand::replaceNodeWithSpanPreservingChildrenAndAttributes(PassRefPtr<Node> node) +HTMLElement* CompositeEditCommand::replaceElementWithSpanPreservingChildrenAndAttributes(PassRefPtr<HTMLElement> node) { // It would also be possible to implement all of ReplaceNodeWithSpanCommand // as a series of existing smaller edit commands. Someone who wanted to @@ -250,7 +254,7 @@ void CompositeEditCommand::prune(PassRefPtr<Node> node) if (renderer && (!renderer->canHaveChildren() || hasARenderedDescendant(node.get()) || node->rootEditableElement() == node)) return; - RefPtr<Node> next = node->parentNode(); + RefPtr<ContainerNode> next = node->parentNode(); removeNode(node); node = next; } @@ -859,7 +863,8 @@ void CompositeEditCommand::moveParagraphWithClones(const VisiblePosition& startO beforeParagraph = VisiblePosition(beforeParagraph.deepEquivalent()); afterParagraph = VisiblePosition(afterParagraph.deepEquivalent()); - if (beforeParagraph.isNotNull() && !isTableElement(beforeParagraph.deepEquivalent().node()) && (!isEndOfParagraph(beforeParagraph) || beforeParagraph == afterParagraph)) { + if (beforeParagraph.isNotNull() && !isTableElement(beforeParagraph.deepEquivalent().node()) + && ((!isEndOfParagraph(beforeParagraph) && !isStartOfParagraph(beforeParagraph)) || beforeParagraph == afterParagraph)) { // FIXME: Trim text between beforeParagraph and afterParagraph if they aren't equal. insertNodeAt(createBreakElement(document()), beforeParagraph.deepEquivalent()); } @@ -1000,7 +1005,7 @@ bool CompositeEditCommand::breakOutOfEmptyListItem() RefPtr<CSSMutableStyleDeclaration> style = ApplyStyleCommand::editingStyleAtPosition(endingSelection().start(), IncludeTypingStyle); - Node* listNode = emptyListItem->parentNode(); + ContainerNode* listNode = emptyListItem->parentNode(); // FIXME: Can't we do something better when the immediate parent wasn't a list node? if (!listNode || (!listNode->hasTagName(ulTag) && !listNode->hasTagName(olTag)) @@ -1009,7 +1014,7 @@ bool CompositeEditCommand::breakOutOfEmptyListItem() return false; RefPtr<Element> newBlock = 0; - if (Node* blockEnclosingList = listNode->parentNode()) { + if (ContainerNode* blockEnclosingList = listNode->parentNode()) { if (blockEnclosingList->hasTagName(liTag)) { // listNode is inside another list item if (visiblePositionAfterNode(blockEnclosingList) == visiblePositionAfterNode(listNode)) { // If listNode appears at the end of the outer list item, then move listNode outside of this list item @@ -1100,7 +1105,7 @@ bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph() } else { ASSERT(caretPos.deprecatedEditingOffset() == 0); Text* textNode = static_cast<Text*>(caretPos.node()); - Node* parentNode = textNode->parentNode(); + ContainerNode* parentNode = textNode->parentNode(); // The preserved newline must be the first thing in the node, since otherwise the previous // paragraph would be quoted, and we verified that it wasn't above. deleteTextFromNode(textNode, 0, 1); diff --git a/WebCore/editing/CompositeEditCommand.h b/WebCore/editing/CompositeEditCommand.h index f839a72..b4c3b2d 100644 --- a/WebCore/editing/CompositeEditCommand.h +++ b/WebCore/editing/CompositeEditCommand.h @@ -39,10 +39,12 @@ class Text; class CompositeEditCommand : public EditCommand { public: + virtual ~CompositeEditCommand(); + bool isFirstCommand(EditCommand* command) { return !m_commands.isEmpty() && m_commands.first() == command; } protected: - CompositeEditCommand(Document*); + explicit CompositeEditCommand(Document*); // // sugary-sweet convenience functions to help create and apply edit commands in composite commands @@ -73,7 +75,7 @@ protected: void removeNodeAttribute(PassRefPtr<Element>, const QualifiedName& attribute); void removeChildrenInRange(PassRefPtr<Node>, unsigned from, unsigned to); virtual void removeNode(PassRefPtr<Node>); - HTMLElement* replaceNodeWithSpanPreservingChildrenAndAttributes(PassRefPtr<Node>); + HTMLElement* replaceElementWithSpanPreservingChildrenAndAttributes(PassRefPtr<HTMLElement>); void removeNodePreservingChildren(PassRefPtr<Node>); void removeNodeAndPruneAncestors(PassRefPtr<Node>); void prune(PassRefPtr<Node>); diff --git a/WebCore/editing/DeleteButtonController.cpp b/WebCore/editing/DeleteButtonController.cpp index 8b23eaa..028edc8 100644 --- a/WebCore/editing/DeleteButtonController.cpp +++ b/WebCore/editing/DeleteButtonController.cpp @@ -121,7 +121,7 @@ static bool isDeletableElement(const Node* node) return true; // Allow blocks that have a different background from it's parent - Node* parentNode = node->parentNode(); + ContainerNode* parentNode = node->parentNode(); if (!parentNode) return false; diff --git a/WebCore/editing/EditingAllInOne.cpp b/WebCore/editing/EditingAllInOne.cpp index dda2501..ba484be 100644 --- a/WebCore/editing/EditingAllInOne.cpp +++ b/WebCore/editing/EditingAllInOne.cpp @@ -26,6 +26,7 @@ // This all-in-one cpp file cuts down on template bloat to allow us to build our Windows release build. #include <AppendNodeCommand.cpp> +#include <ApplyBlockElementCommand.cpp> #include <ApplyStyleCommand.cpp> #include <BreakBlockquoteCommand.cpp> #include <CompositeEditCommand.cpp> @@ -47,6 +48,7 @@ #include <InsertParagraphSeparatorCommand.cpp> #include <InsertTextCommand.cpp> #include <JoinTextNodesCommand.cpp> +#include <MarkupAccumulator.cpp> #include <MergeIdenticalElementsCommand.cpp> #include <ModifySelectionListLevel.cpp> #include <MoveSelectionCommand.cpp> diff --git a/WebCore/editing/Editor.cpp b/WebCore/editing/Editor.cpp index b267637..7b3d055 100644 --- a/WebCore/editing/Editor.cpp +++ b/WebCore/editing/Editor.cpp @@ -95,10 +95,14 @@ VisibleSelection Editor::selectionForCommand(Event* event) Node* target = event->target()->toNode(); Node* selectionStart = selection.start().node(); if (target && (!selectionStart || target->shadowAncestorNode() != selectionStart->shadowAncestorNode())) { + RefPtr<Range> range; if (target->hasTagName(inputTag) && static_cast<HTMLInputElement*>(target)->isTextField()) - return static_cast<HTMLInputElement*>(target)->selection(); - if (target->hasTagName(textareaTag)) - return static_cast<HTMLTextAreaElement*>(target)->selection(); + range = static_cast<HTMLInputElement*>(target)->selection(); + else if (target->hasTagName(textareaTag)) + range = static_cast<HTMLTextAreaElement*>(target)->selection(); + + if (range) + return VisibleSelection(range.get()); } return selection; } @@ -643,7 +647,7 @@ bool Editor::hasBidiSelection() const return false; RenderStyle* style = renderer->style(); - if (style->direction() == RTL) + if (!style->isLeftToRightDirection()) return true; return toRenderBlock(renderer)->containsNonZeroBidiLevel(); @@ -2373,6 +2377,49 @@ void Editor::markMisspellingsAndBadGrammar(const VisibleSelection &movingSelecti void Editor::markMisspellingsAfterTypingToPosition(const VisiblePosition &p) { #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) +#if !defined(BUILDING_ON_SNOW_LEOPARD) + // Apply pending autocorrection before next round of spell checking. + bool didApplyCorrection = false; + if (m_rangeToBeReplacedByCorrection) { + ExceptionCode ec = 0; + RefPtr<Range> paragraphRangeContainingCorrection = m_rangeToBeReplacedByCorrection->cloneRange(ec); + if (!ec) { + setStart(paragraphRangeContainingCorrection.get(), startOfParagraph(m_rangeToBeReplacedByCorrection->startPosition())); + setEnd(paragraphRangeContainingCorrection.get(), endOfParagraph(m_rangeToBeReplacedByCorrection->endPosition())); + // After we replace the word at range m_rangeToBeReplacedByCorrection, we need to add + // autocorrection underline at that range. However, once the replacement took place, the + // value of m_rangeToBeReplacedByCorrection is not valid anymore. So before we carry out + // the replacement, we need to store the start position of m_rangeToBeReplacedByCorrection + // relative to the start position of the containing paragraph. We use correctionStartOffsetInParagraph + // to store this value. In order to obtain this offset, we need to first create a range + // which spans from the start of paragraph to the start position of m_rangeToBeReplacedByCorrection. + RefPtr<Range> correctionStartOffsetInParagraphAsRange = Range::create(paragraphRangeContainingCorrection->startContainer(ec)->document(), paragraphRangeContainingCorrection->startPosition(), paragraphRangeContainingCorrection->startPosition()); + if (!ec) { + Position startPositionOfRangeToBeReplaced = m_rangeToBeReplacedByCorrection->startPosition(); + correctionStartOffsetInParagraphAsRange->setEnd(startPositionOfRangeToBeReplaced.containerNode(), startPositionOfRangeToBeReplaced.computeOffsetInContainerNode(), ec); + if (!ec) { + // Take note of the location of autocorrection so that we can add marker after the replacement took place. + int correctionStartOffsetInParagraph = TextIterator::rangeLength(correctionStartOffsetInParagraphAsRange.get()); + Position caretPosition = m_frame->selection()->selection().end(); + RefPtr<Range> rangeToBeReplaced = m_rangeToBeReplacedByCorrection->cloneRange(ec); + VisibleSelection selectionToReplace(rangeToBeReplaced.get(), DOWNSTREAM); + if (m_frame->selection()->shouldChangeSelection(selectionToReplace)) { + m_frame->selection()->setSelection(selectionToReplace); + replaceSelectionWithText(m_correctionReplacementString, false, false); + caretPosition.moveToOffset(caretPosition.offsetInContainerNode() + m_correctionReplacementString.length() - m_stringToBeReplacedByCorrection.length()); + RefPtr<Range> replacementRange = TextIterator::subrange(paragraphRangeContainingCorrection.get(), correctionStartOffsetInParagraph, m_correctionReplacementString.length()); + replacementRange->startContainer()->document()->markers()->addMarker(replacementRange.get(), DocumentMarker::Replacement, m_correctionReplacementString); + replacementRange->startContainer()->document()->markers()->addMarker(replacementRange.get(), DocumentMarker::CorrectionIndicator); + m_frame->selection()->moveTo(caretPosition, false); + didApplyCorrection = true; + } + } + } + } + m_rangeToBeReplacedByCorrection.clear(); + } +#endif + TextCheckingOptions textCheckingOptions = 0; if (isContinuousSpellCheckingEnabled()) textCheckingOptions |= MarkSpelling; @@ -2723,6 +2770,7 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh totalBoundingBox.unite(it->boundingBox()); m_rangeToBeReplacedByCorrection = rangeToReplace; m_stringToBeReplacedByCorrection = replacedString; + m_correctionReplacementString = result->replacement; client()->showCorrectionPanel(totalBoundingBox, m_stringToBeReplacedByCorrection, result->replacement, this); doReplacement = false; } @@ -2734,10 +2782,6 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh if (resultLocation < selectionOffset) selectionOffset += replacementLength - resultLength; if (result->type == TextCheckingTypeCorrection) { -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) - if (client()) - client()->dismissCorrectionPanel(true); -#endif // Add a marker so that corrections can easily be undone and won't be re-corrected. RefPtr<Range> replacedRange = TextIterator::subrange(paragraphRange.get(), resultLocation, replacementLength); replacedRange->startContainer()->document()->markers()->addMarker(replacedRange.get(), DocumentMarker::Replacement, replacedString); @@ -2826,10 +2870,10 @@ void Editor::startCorrectionPanelTimer() { #if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD) static const double correctionPanelTimerInterval = 0.3; - if (client()) - client()->dismissCorrectionPanel(true); - if (isAutomaticSpellingCorrectionEnabled()) + if (isAutomaticSpellingCorrectionEnabled()) { + m_rangeToBeReplacedByCorrection.clear(); m_correctionPanelTimer.startOneShot(correctionPanelTimerInterval); + } #endif } @@ -3565,12 +3609,12 @@ void Editor::respondToChangedSelection(const VisibleSelection& oldSelection, boo } } +#if !PLATFORM(MAC) || (PLATFORM(MAC) && (defined(BUILDING_ON_TIGER) || defined(BUILDING_ON_LEOPARD) || defined(BUILDING_ON_SNOW_LEOPARD))) // This only erases markers that are in the first unit (word or sentence) of the selection. - // Perhaps peculiar, but it matches AppKit. - if (RefPtr<Range> wordRange = newAdjacentWords.toNormalizedRange()) { + // Perhaps peculiar, but it matches AppKit on these Mac OSX versions. + if (RefPtr<Range> wordRange = newAdjacentWords.toNormalizedRange()) m_frame->document()->markers()->removeMarkers(wordRange.get(), DocumentMarker::Spelling); - m_frame->document()->markers()->removeMarkers(wordRange.get(), DocumentMarker::Replacement); - } +#endif if (RefPtr<Range> sentenceRange = newSelectedSentence.toNormalizedRange()) m_frame->document()->markers()->removeMarkers(sentenceRange.get(), DocumentMarker::Grammar); } diff --git a/WebCore/editing/Editor.h b/WebCore/editing/Editor.h index f167b16..df2e947 100644 --- a/WebCore/editing/Editor.h +++ b/WebCore/editing/Editor.h @@ -378,6 +378,7 @@ private: OwnPtr<KillRing> m_killRing; RefPtr<Range> m_rangeToBeReplacedByCorrection; String m_stringToBeReplacedByCorrection; + String m_correctionReplacementString; Timer<Editor> m_correctionPanelTimer; VisibleSelection m_mark; bool m_areMarkedTextMatchesHighlighted; diff --git a/WebCore/editing/EditorCommand.cpp b/WebCore/editing/EditorCommand.cpp index 616c07b..23fba39 100644 --- a/WebCore/editing/EditorCommand.cpp +++ b/WebCore/editing/EditorCommand.cpp @@ -432,10 +432,16 @@ static bool executeFormatBlock(Frame* frame, Event*, EditorCommandSource, const String tagName = value.lower(); if (tagName[0] == '<' && tagName[tagName.length() - 1] == '>') tagName = tagName.substring(1, tagName.length() - 2); - if (!validBlockTag(tagName)) + + ExceptionCode ec; + String localName, prefix; + if (!Document::parseQualifiedName(tagName, prefix, localName, ec)) return false; - applyCommand(FormatBlockCommand::create(frame->document(), tagName)); - return true; + QualifiedName qualifiedTagName(prefix, localName, xhtmlNamespaceURI); + + RefPtr<FormatBlockCommand> command = FormatBlockCommand::create(frame->document(), qualifiedTagName); + applyCommand(command); + return command->didApply(); } static bool executeForwardDelete(Frame* frame, Event*, EditorCommandSource source, const String&) @@ -1304,6 +1310,11 @@ static TriState stateJustifyCenter(Frame* frame, Event*) return stateStyle(frame, CSSPropertyTextAlign, "center"); } +static TriState stateJustifyFull(Frame* frame, Event*) +{ + return stateStyle(frame, CSSPropertyTextAlign, "justify"); +} + static TriState stateJustifyLeft(Frame* frame, Event*) { return stateStyle(frame, CSSPropertyTextAlign, "left"); @@ -1346,6 +1357,17 @@ static String valueForeColor(Frame* frame, Event*) return valueStyle(frame, CSSPropertyColor); } +static String valueFormatBlock(Frame* frame, Event*) +{ + const VisibleSelection& selection = frame->selection()->selection(); + if (!selection.isNonOrphanedCaretOrRange() || !selection.isContentEditable()) + return ""; + Element* formatBlockElement = FormatBlockCommand::elementForFormatBlockCommand(selection.firstRange().get()); + if (!formatBlockElement) + return ""; + return formatBlockElement->localName(); +} + // Map of functions struct CommandEntry { @@ -1382,7 +1404,7 @@ static const CommandMap& createCommandMap() { "FontSize", { executeFontSize, supported, enabledInEditableText, stateNone, valueFontSize, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "FontSizeDelta", { executeFontSizeDelta, supported, enabledInEditableText, stateNone, valueFontSizeDelta, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "ForeColor", { executeForeColor, supported, enabledInRichlyEditableText, stateNone, valueForeColor, notTextInsertion, doNotAllowExecutionWhenDisabled } }, - { "FormatBlock", { executeFormatBlock, supported, enabledInRichlyEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, + { "FormatBlock", { executeFormatBlock, supported, enabledInRichlyEditableText, stateNone, valueFormatBlock, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "ForwardDelete", { executeForwardDelete, supported, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "HiliteColor", { executeBackColor, supported, enabledInRichlyEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "IgnoreSpelling", { executeIgnoreSpelling, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, @@ -1401,7 +1423,7 @@ static const CommandMap& createCommandMap() { "InsertUnorderedList", { executeInsertUnorderedList, supported, enabledInRichlyEditableText, stateUnorderedList, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "Italic", { executeToggleItalic, supported, enabledInRichlyEditableText, stateItalic, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "JustifyCenter", { executeJustifyCenter, supported, enabledInRichlyEditableText, stateJustifyCenter, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, - { "JustifyFull", { executeJustifyFull, supported, enabledInRichlyEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, + { "JustifyFull", { executeJustifyFull, supported, enabledInRichlyEditableText, stateJustifyFull, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "JustifyLeft", { executeJustifyLeft, supported, enabledInRichlyEditableText, stateJustifyLeft, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "JustifyNone", { executeJustifyLeft, supported, enabledInRichlyEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "JustifyRight", { executeJustifyRight, supported, enabledInRichlyEditableText, stateJustifyRight, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, diff --git a/WebCore/editing/FormatBlockCommand.cpp b/WebCore/editing/FormatBlockCommand.cpp index 6bb8ad5..2e42fb3 100644 --- a/WebCore/editing/FormatBlockCommand.cpp +++ b/WebCore/editing/FormatBlockCommand.cpp @@ -30,101 +30,126 @@ #include "htmlediting.h" #include "HTMLElement.h" #include "HTMLNames.h" +#include "Range.h" #include "visible_units.h" namespace WebCore { using namespace HTMLNames; -FormatBlockCommand::FormatBlockCommand(Document* document, const AtomicString& tagName) - : CompositeEditCommand(document), m_tagName(tagName) +static Node* enclosingBlockToSplitTreeTo(Node* startNode); +static bool isElementForFormatBlock(const QualifiedName& tagName); +static inline bool isElementForFormatBlock(Node* node) { + return node->isElementNode() && isElementForFormatBlock(static_cast<Element*>(node)->tagQName()); } -void FormatBlockCommand::doApply() +FormatBlockCommand::FormatBlockCommand(Document* document, const QualifiedName& tagName) + : ApplyBlockElementCommand(document, tagName) + , m_didApply(false) { - if (!endingSelection().isNonOrphanedCaretOrRange()) - return; - - if (!endingSelection().rootEditableElement()) +} + +void FormatBlockCommand::formatSelection(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection) +{ + if (!isElementForFormatBlock(tagName())) return; + ApplyBlockElementCommand::formatSelection(startOfSelection, endOfSelection); + m_didApply = true; +} - VisiblePosition visibleEnd = endingSelection().visibleEnd(); - VisiblePosition visibleStart = endingSelection().visibleStart(); - // When a selection ends at the start of a paragraph, we rarely paint - // the selection gap before that paragraph, because there often is no gap. - // In a case like this, it's not obvious to the user that the selection - // ends "inside" that paragraph, so it would be confusing if FormatBlock - // operated on that paragraph. - // FIXME: We paint the gap before some paragraphs that are indented with left - // margin/padding, but not others. We should make the gap painting more consistent and - // then use a left margin/padding rule here. - if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd)) { - setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(true))); - visibleEnd = endingSelection().visibleEnd(); - } +void FormatBlockCommand::formatRange(const Position& start, const Position& end, RefPtr<Element>& blockNode) +{ + Node* nodeToSplitTo = enclosingBlockToSplitTreeTo(start.node()); + RefPtr<Node> outerBlock = (start.node() == nodeToSplitTo) ? start.node() : splitTreeToNode(start.node(), nodeToSplitTo); + RefPtr<Node> nodeAfterInsertionPosition = outerBlock; - VisiblePosition startOfLastParagraph = startOfParagraph(visibleEnd); - if (endingSelection().isCaret() || startOfParagraph(visibleStart) == startOfLastParagraph) { - doApplyForSingleParagraph(); - return; + Element* refNode = enclosingBlockFlowElement(end); + Element* root = editableRootForPosition(start); + if (isElementForFormatBlock(refNode->tagQName()) && start == startOfBlock(start) && end == endOfBlock(end) + && refNode != root && !root->isDescendantOf(refNode)) { + // Already in a block element that only contains the current paragraph + if (refNode->hasTagName(tagName())) + return; + nodeAfterInsertionPosition = refNode; } - setEndingSelection(visibleStart); - doApplyForSingleParagraph(); - visibleStart = endingSelection().visibleStart(); - VisiblePosition nextParagraph = endOfParagraph(visibleStart).next(); - while (nextParagraph.isNotNull() && nextParagraph != startOfLastParagraph) { - setEndingSelection(nextParagraph); - doApplyForSingleParagraph(); - nextParagraph = endOfParagraph(endingSelection().visibleStart()).next(); + if (!blockNode) { + // Create a new blockquote and insert it as a child of the root editable element. We accomplish + // this by splitting all parents of the current paragraph up to that point. + blockNode = createBlockElement(); + insertNodeBefore(blockNode, nodeAfterInsertionPosition); } - setEndingSelection(visibleEnd); - doApplyForSingleParagraph(); - visibleEnd = endingSelection().visibleEnd(); - setEndingSelection(VisibleSelection(visibleStart.deepEquivalent(), visibleEnd.deepEquivalent(), DOWNSTREAM)); -} + Position lastParagraphInBlockNode = lastPositionInNode(blockNode.get()); + bool wasEndOfParagraph = isEndOfParagraph(lastParagraphInBlockNode); + + moveParagraphWithClones(start, end, blockNode.get(), outerBlock.get()); -void FormatBlockCommand::doApplyForSingleParagraph() + if (wasEndOfParagraph && !isEndOfParagraph(lastParagraphInBlockNode) && !isStartOfParagraph(lastParagraphInBlockNode)) + insertBlockPlaceholder(lastParagraphInBlockNode); +} + +Element* FormatBlockCommand::elementForFormatBlockCommand(Range* range) { + if (!range) + return 0; + ExceptionCode ec; - String localName, prefix; - if (!Document::parseQualifiedName(m_tagName, prefix, localName, ec)) - return; - QualifiedName qTypeOfBlock(prefix, localName, xhtmlNamespaceURI); + Node* commonAncestor = range->commonAncestorContainer(ec); + while (commonAncestor && !isElementForFormatBlock(commonAncestor)) + commonAncestor = commonAncestor->parentNode(); - Node* refNode = enclosingBlockFlowElement(endingSelection().visibleStart()); - if (refNode->hasTagName(qTypeOfBlock)) - // We're already in a block with the format we want, so we don't have to do anything - return; + if (!commonAncestor) + return 0; - VisiblePosition paragraphStart = startOfParagraph(endingSelection().visibleStart()); - VisiblePosition paragraphEnd = endOfParagraph(endingSelection().visibleStart()); - VisiblePosition blockStart = startOfBlock(endingSelection().visibleStart()); - VisiblePosition blockEnd = endOfBlock(endingSelection().visibleStart()); - RefPtr<Element> blockNode = createHTMLElement(document(), m_tagName); - RefPtr<Element> placeholder = createBreakElement(document()); - - Node* root = endingSelection().start().node()->rootEditableElement(); - if (validBlockTag(refNode->nodeName().lower()) && - paragraphStart == blockStart && paragraphEnd == blockEnd && - refNode != root && !root->isDescendantOf(refNode)) - // Already in a valid block tag that only contains the current paragraph, so we can swap with the new tag - insertNodeBefore(blockNode, refNode); - else { - // Avoid inserting inside inline elements that surround paragraphStart with upstream(). - // This is only to avoid creating bloated markup. - insertNodeAt(blockNode, paragraphStart.deepEquivalent().upstream()); + ASSERT(commonAncestor->isElementNode()); + return static_cast<Element*>(commonAncestor); +} + +bool isElementForFormatBlock(const QualifiedName& tagName) +{ + DEFINE_STATIC_LOCAL(HashSet<QualifiedName>, blockTags, ()); + if (blockTags.isEmpty()) { + blockTags.add(addressTag); + blockTags.add(articleTag); + blockTags.add(asideTag); + blockTags.add(blockquoteTag); + blockTags.add(ddTag); + blockTags.add(divTag); + blockTags.add(dlTag); + blockTags.add(dtTag); + blockTags.add(footerTag); + blockTags.add(h1Tag); + blockTags.add(h2Tag); + blockTags.add(h3Tag); + blockTags.add(h4Tag); + blockTags.add(h5Tag); + blockTags.add(h6Tag); + blockTags.add(headerTag); + blockTags.add(hgroupTag); + blockTags.add(navTag); + blockTags.add(pTag); + blockTags.add(preTag); + blockTags.add(sectionTag); } - appendNode(placeholder, blockNode); + return blockTags.contains(tagName); +} - VisiblePosition destination(Position(placeholder.get(), 0)); - if (paragraphStart == paragraphEnd && !lineBreakExistsAtVisiblePosition(paragraphStart)) { - setEndingSelection(destination); - return; +Node* enclosingBlockToSplitTreeTo(Node* startNode) +{ + Node* lastBlock = startNode; + for (Node* n = startNode; n; n = n->parentNode()) { + if (!n->isContentEditable()) + return lastBlock; + if (isTableCell(n) || n->hasTagName(bodyTag) || !n->parentNode() || !n->parentNode()->isContentEditable() || isElementForFormatBlock(n)) + return n; + if (isBlock(n)) + lastBlock = n; + if (isListElement(n)) + return n->parentNode()->isContentEditable() ? n->parentNode() : n; } - moveParagraph(paragraphStart, paragraphEnd, destination, true, false); + return lastBlock; } } diff --git a/WebCore/editing/FormatBlockCommand.h b/WebCore/editing/FormatBlockCommand.h index 70e6fa6..134a422 100644 --- a/WebCore/editing/FormatBlockCommand.h +++ b/WebCore/editing/FormatBlockCommand.h @@ -26,25 +26,29 @@ #ifndef FormatBlockCommand_h #define FormatBlockCommand_h +#include "ApplyBlockElementCommand.h" #include "CompositeEditCommand.h" namespace WebCore { -class FormatBlockCommand : public CompositeEditCommand { +class FormatBlockCommand : public ApplyBlockElementCommand { public: - static PassRefPtr<FormatBlockCommand> create(Document* document, const AtomicString& tagName) + static PassRefPtr<FormatBlockCommand> create(Document* document, const QualifiedName& tagName) { return adoptRef(new FormatBlockCommand(document, tagName)); } + static Element* elementForFormatBlockCommand(Range*); + bool didApply() const { return m_didApply; } + private: - FormatBlockCommand(Document*, const AtomicString& tagName); + FormatBlockCommand(Document*, const QualifiedName& tagName); - virtual void doApply(); - void doApplyForSingleParagraph(); - virtual EditAction editingAction() const { return EditActionFormatBlock; } + void formatSelection(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection); + void formatRange(const Position&, const Position&, RefPtr<Element>&); + EditAction editingAction() const { return EditActionFormatBlock; } - AtomicString m_tagName; + bool m_didApply; }; } // namespace WebCore diff --git a/WebCore/editing/IndentOutdentCommand.cpp b/WebCore/editing/IndentOutdentCommand.cpp index d4ffe7f..9642afa 100644 --- a/WebCore/editing/IndentOutdentCommand.cpp +++ b/WebCore/editing/IndentOutdentCommand.cpp @@ -44,50 +44,22 @@ namespace WebCore { using namespace HTMLNames; -static String indentBlockquoteString() -{ - DEFINE_STATIC_LOCAL(String, string, ("webkit-indent-blockquote")); - return string; -} - -static PassRefPtr<HTMLBlockquoteElement> createIndentBlockquoteElement(Document* document) -{ - RefPtr<HTMLBlockquoteElement> element = HTMLBlockquoteElement::create(document); - element->setAttribute(classAttr, indentBlockquoteString()); - element->setAttribute(styleAttr, "margin: 0 0 0 40px; border: none; padding: 0px;"); - return element.release(); -} - static bool isListOrIndentBlockquote(const Node* node) { return node && (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(blockquoteTag)); } -// This function can return -1 if we are unable to count the paragraphs between |start| and |end|. -static int countParagraphs(const VisiblePosition& endOfFirstParagraph, const VisiblePosition& endOfLastParagraph) -{ - int count = 0; - VisiblePosition cur = endOfFirstParagraph; - while (cur != endOfLastParagraph) { - ++count; - cur = endOfParagraph(cur.next()); - // If start is before a table and end is inside a table, we will never hit end because the - // whole table is considered a single paragraph. - if (cur.isNull()) - return -1; - } - return count; -} - IndentOutdentCommand::IndentOutdentCommand(Document* document, EIndentType typeOfAction, int marginInPixels) - : CompositeEditCommand(document), m_typeOfAction(typeOfAction), m_marginInPixels(marginInPixels) + : ApplyBlockElementCommand(document, blockquoteTag, "webkit-indent-blockquote", "margin: 0 0 0 40px; border: none; padding: 0px;") + , m_typeOfAction(typeOfAction) + , m_marginInPixels(marginInPixels) { } -bool IndentOutdentCommand::tryIndentingAsListItem(const VisiblePosition& endOfCurrentParagraph) +bool IndentOutdentCommand::tryIndentingAsListItem(const Position& start, const Position& end) { // If our selection is not inside a list, bail out. - Node* lastNodeInSelectedParagraph = endOfCurrentParagraph.deepEquivalent().node(); + Node* lastNodeInSelectedParagraph = start.node(); RefPtr<Element> listNode = enclosingList(lastNodeInSelectedParagraph); if (!listNode) return false; @@ -106,7 +78,7 @@ bool IndentOutdentCommand::tryIndentingAsListItem(const VisiblePosition& endOfCu RefPtr<Element> newList = document()->createElement(listNode->tagQName(), false); insertNodeBefore(newList, selectedListItem); - moveParagraphWithClones(startOfParagraph(endOfCurrentParagraph), endOfCurrentParagraph, newList.get(), selectedListItem); + moveParagraphWithClones(start, end, newList.get(), selectedListItem); if (canMergeLists(previousList, newList.get())) mergeIdenticalElements(previousList, newList); @@ -115,13 +87,10 @@ bool IndentOutdentCommand::tryIndentingAsListItem(const VisiblePosition& endOfCu return true; } - -void IndentOutdentCommand::indentIntoBlockquote(const VisiblePosition& endOfCurrentParagraph, const VisiblePosition& endOfNextParagraph, RefPtr<Element>& targetBlockquote) -{ - Node* enclosingCell = 0; - Position start = startOfParagraph(endOfCurrentParagraph).deepEquivalent(); - enclosingCell = enclosingNodeOfType(start, &isTableCell); +void IndentOutdentCommand::indentIntoBlockquote(const Position& start, const Position& end, RefPtr<Element>& targetBlockquote) +{ + Node* enclosingCell = enclosingNodeOfType(start, &isTableCell); Node* nodeToSplitTo; if (enclosingCell) nodeToSplitTo = enclosingCell; @@ -135,97 +104,11 @@ void IndentOutdentCommand::indentIntoBlockquote(const VisiblePosition& endOfCurr if (!targetBlockquote) { // Create a new blockquote and insert it as a child of the root editable element. We accomplish // this by splitting all parents of the current paragraph up to that point. - targetBlockquote = createIndentBlockquoteElement(document()); + targetBlockquote = createBlockElement(); insertNodeBefore(targetBlockquote, outerBlock); } - moveParagraphWithClones(startOfParagraph(endOfCurrentParagraph), endOfCurrentParagraph, targetBlockquote.get(), outerBlock.get()); - - // Don't put the next paragraph in the blockquote we just created for this paragraph unless - // the next paragraph is in the same cell. - if (enclosingCell && enclosingCell != enclosingNodeOfType(endOfNextParagraph.deepEquivalent(), &isTableCell)) - targetBlockquote = 0; -} - -void IndentOutdentCommand::indentRegion(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection) -{ - // Special case empty unsplittable elements because there's nothing to split - // and there's nothing to move. - Position start = startOfSelection.deepEquivalent().downstream(); - if (isAtUnsplittableElement(start)) { - RefPtr<Element> blockquote = createIndentBlockquoteElement(document()); - insertNodeAt(blockquote, start); - RefPtr<Element> placeholder = createBreakElement(document()); - appendNode(placeholder, blockquote); - setEndingSelection(VisibleSelection(Position(placeholder.get(), 0), DOWNSTREAM)); - return; - } - - RefPtr<Element> blockquoteForNextIndent; - VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection); - VisiblePosition endAfterSelection = endOfParagraph(endOfParagraph(endOfSelection).next()); - int endOfCurrentParagraphIndex = indexForVisiblePosition(endOfCurrentParagraph); - int endAfterSelectionIndex = indexForVisiblePosition(endAfterSelection); - - // When indenting within a <pre> tag, we need to split each paragraph into a separate node for moveParagraphWithClones to work. - // However, splitting text nodes can cause endOfCurrentParagraph and endAfterSelection to point to an invalid position if we - // changed the text node it was pointing at. So we have to reset these positions. - int numParagraphs = countParagraphs(endOfCurrentParagraph, endAfterSelection); - if (splitTextNodes(startOfParagraph(startOfSelection), numParagraphs + 1)) { - RefPtr<Range> endOfCurrentParagraphRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), endOfCurrentParagraphIndex, 0, true); - RefPtr<Range> endAfterSelectionRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), endAfterSelectionIndex, 0, true); - if (!endOfCurrentParagraphRange.get() || !endAfterSelectionRange.get()) { - ASSERT_NOT_REACHED(); - return; - } - endOfCurrentParagraph = VisiblePosition(endOfCurrentParagraphRange->startPosition(), DOWNSTREAM); - endAfterSelection = VisiblePosition(endAfterSelectionRange->startPosition(), DOWNSTREAM); - } - - while (endOfCurrentParagraph != endAfterSelection) { - // Iterate across the selected paragraphs... - VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next()); - if (tryIndentingAsListItem(endOfCurrentParagraph)) - blockquoteForNextIndent = 0; - else - indentIntoBlockquote(endOfCurrentParagraph, endOfNextParagraph, blockquoteForNextIndent); - - // indentIntoBlockquote could move more than one paragraph if the paragraph - // is in a list item or a table. As a result, endAfterSelection could refer to a position - // no longer in the document. - if (endAfterSelection.isNotNull() && !endAfterSelection.deepEquivalent().node()->inDocument()) - break; - // Sanity check: Make sure our moveParagraph calls didn't remove endOfNextParagraph.deepEquivalent().node() - // If somehow we did, return to prevent crashes. - if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent().node()->inDocument()) { - ASSERT_NOT_REACHED(); - return; - } - endOfCurrentParagraph = endOfNextParagraph; - } -} - -// Returns true if at least one text node was split. -bool IndentOutdentCommand::splitTextNodes(const VisiblePosition& start, int numParagraphs) -{ - VisiblePosition currentParagraphStart = start; - bool hasSplit = false; - int paragraphCount; - for (paragraphCount = 0; paragraphCount < numParagraphs; ++paragraphCount) { - // If there are multiple paragraphs in a single text node, we split the text node into a separate node for each paragraph. - if (currentParagraphStart.deepEquivalent().node()->isTextNode() && currentParagraphStart.deepEquivalent().node() == startOfParagraph(currentParagraphStart.previous()).deepEquivalent().node()) { - Text* textNode = static_cast<Text *>(currentParagraphStart.deepEquivalent().node()); - int offset = currentParagraphStart.deepEquivalent().offsetInContainerNode(); - splitTextNode(textNode, offset); - currentParagraphStart = VisiblePosition(textNode, 0, VP_DEFAULT_AFFINITY); - hasSplit = true; - } - VisiblePosition nextParagraph = startOfParagraph(endOfParagraph(currentParagraphStart).next()); - if (nextParagraph.isNull()) - break; - currentParagraphStart = nextParagraph; - } - return hasSplit; + moveParagraphWithClones(start, end, targetBlockquote.get(), outerBlock.get()); } void IndentOutdentCommand::outdentParagraph() @@ -260,7 +143,7 @@ void IndentOutdentCommand::outdentParagraph() // outdentRegion() assumes it is operating on the first paragraph of an enclosing blockquote, but if there are multiply nested blockquotes and we've // just removed one, then this assumption isn't true. By splitting the next containing blockquote after this node, we keep this assumption true if (splitPoint) { - if (Node* splitPointParent = splitPoint->parentNode()) { + if (ContainerNode* splitPointParent = splitPoint->parentNode()) { if (splitPointParent->hasTagName(blockquoteTag) && !splitPoint->hasTagName(blockquoteTag) && splitPointParent->parentNode()->isContentEditable()) // We can't outdent if there is no place to go! @@ -291,6 +174,7 @@ void IndentOutdentCommand::outdentParagraph() moveParagraph(startOfParagraph(visibleStartOfParagraph), endOfParagraph(visibleEndOfParagraph), VisiblePosition(Position(placeholder.get(), 0)), true); } +// FIXME: We should merge this function with ApplyBlockElementCommand::formatSelection void IndentOutdentCommand::outdentRegion(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection) { VisiblePosition endOfLastParagraph = endOfParagraph(endOfSelection); @@ -327,48 +211,20 @@ void IndentOutdentCommand::outdentRegion(const VisiblePosition& startOfSelection } } -void IndentOutdentCommand::doApply() +void IndentOutdentCommand::formatSelection(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection) { - if (!endingSelection().isNonOrphanedCaretOrRange()) - return; - - if (!endingSelection().rootEditableElement()) - return; - - VisiblePosition visibleEnd = endingSelection().visibleEnd(); - VisiblePosition visibleStart = endingSelection().visibleStart(); - // When a selection ends at the start of a paragraph, we rarely paint - // the selection gap before that paragraph, because there often is no gap. - // In a case like this, it's not obvious to the user that the selection - // ends "inside" that paragraph, so it would be confusing if Indent/Outdent - // operated on that paragraph. - // FIXME: We paint the gap before some paragraphs that are indented with left - // margin/padding, but not others. We should make the gap painting more consistent and - // then use a left margin/padding rule here. - if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd)) - setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(true))); - - VisibleSelection selection = selectionForParagraphIteration(endingSelection()); - VisiblePosition startOfSelection = selection.visibleStart(); - VisiblePosition endOfSelection = selection.visibleEnd(); - - int startIndex = indexForVisiblePosition(startOfSelection); - int endIndex = indexForVisiblePosition(endOfSelection); - - ASSERT(!startOfSelection.isNull()); - ASSERT(!endOfSelection.isNull()); - if (m_typeOfAction == Indent) - indentRegion(startOfSelection, endOfSelection); + ApplyBlockElementCommand::formatSelection(startOfSelection, endOfSelection); else outdentRegion(startOfSelection, endOfSelection); +} - updateLayout(); - - RefPtr<Range> startRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), startIndex, 0, true); - RefPtr<Range> endRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), endIndex, 0, true); - if (startRange && endRange) - setEndingSelection(VisibleSelection(startRange->startPosition(), endRange->startPosition(), DOWNSTREAM)); +void IndentOutdentCommand::formatRange(const Position& start, const Position& end, RefPtr<Element>& blockquoteForNextIndent) +{ + if (tryIndentingAsListItem(start, end)) + blockquoteForNextIndent = 0; + else + indentIntoBlockquote(start, end, blockquoteForNextIndent); } } diff --git a/WebCore/editing/IndentOutdentCommand.h b/WebCore/editing/IndentOutdentCommand.h index 8644cc5..201e794 100644 --- a/WebCore/editing/IndentOutdentCommand.h +++ b/WebCore/editing/IndentOutdentCommand.h @@ -26,32 +26,34 @@ #ifndef IndentOutdentCommand_h #define IndentOutdentCommand_h +#include "ApplyBlockElementCommand.h" #include "CompositeEditCommand.h" namespace WebCore { -class IndentOutdentCommand : public CompositeEditCommand { +class IndentOutdentCommand : public ApplyBlockElementCommand { public: enum EIndentType { Indent, Outdent }; static PassRefPtr<IndentOutdentCommand> create(Document* document, EIndentType type, int marginInPixels = 0) { return adoptRef(new IndentOutdentCommand(document, type, marginInPixels)); } - + virtual bool preservesTypingStyle() const { return true; } private: IndentOutdentCommand(Document*, EIndentType, int marginInPixels); - virtual void doApply(); virtual EditAction editingAction() const { return m_typeOfAction == Indent ? EditActionIndent : EditActionOutdent; } void indentRegion(const VisiblePosition&, const VisiblePosition&); void outdentRegion(const VisiblePosition&, const VisiblePosition&); void outdentParagraph(); - bool tryIndentingAsListItem(const VisiblePosition&); - void indentIntoBlockquote(const VisiblePosition&, const VisiblePosition&, RefPtr<Element>&); - bool splitTextNodes(const VisiblePosition& start, int numParagraphs); + bool tryIndentingAsListItem(const Position&, const Position&); + void indentIntoBlockquote(const Position&, const Position&, RefPtr<Element>&); + + void formatSelection(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection); + void formatRange(const Position&, const Position&, RefPtr<Element>& blockquoteForNextIndent); EIndentType m_typeOfAction; int m_marginInPixels; diff --git a/WebCore/editing/InsertNodeBeforeCommand.cpp b/WebCore/editing/InsertNodeBeforeCommand.cpp index fb72312..5fae45e 100644 --- a/WebCore/editing/InsertNodeBeforeCommand.cpp +++ b/WebCore/editing/InsertNodeBeforeCommand.cpp @@ -46,7 +46,7 @@ InsertNodeBeforeCommand::InsertNodeBeforeCommand(PassRefPtr<Node> insertChild, P void InsertNodeBeforeCommand::doApply() { - Node* parent = m_refChild->parentNode(); + ContainerNode* parent = m_refChild->parentNode(); if (!parent || !parent->isContentEditable()) return; diff --git a/WebCore/editing/JoinTextNodesCommand.cpp b/WebCore/editing/JoinTextNodesCommand.cpp index fa0987d..2766b84 100644 --- a/WebCore/editing/JoinTextNodesCommand.cpp +++ b/WebCore/editing/JoinTextNodesCommand.cpp @@ -45,7 +45,7 @@ void JoinTextNodesCommand::doApply() if (m_text1->nextSibling() != m_text2) return; - Node* parent = m_text2->parentNode(); + ContainerNode* parent = m_text2->parentNode(); if (!parent || !parent->isContentEditable()) return; @@ -62,7 +62,7 @@ void JoinTextNodesCommand::doUnapply() if (m_text1->parentNode()) return; - Node* parent = m_text2->parentNode(); + ContainerNode* parent = m_text2->parentNode(); if (!parent || !parent->isContentEditable()) return; diff --git a/WebCore/editing/MarkupAccumulator.cpp b/WebCore/editing/MarkupAccumulator.cpp new file mode 100644 index 0000000..a701189 --- /dev/null +++ b/WebCore/editing/MarkupAccumulator.cpp @@ -0,0 +1,465 @@ +/* + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2009, 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "MarkupAccumulator.h" + +#include "CDATASection.h" +#include "CharacterNames.h" +#include "Comment.h" +#include "DocumentFragment.h" +#include "DocumentType.h" +#include "Editor.h" +#include "HTMLElement.h" +#include "HTMLNames.h" +#include "KURL.h" +#include "ProcessingInstruction.h" +#include "XMLNSNames.h" + +namespace WebCore { + +using namespace HTMLNames; + +void appendCharactersReplacingEntities(Vector<UChar>& out, const UChar* content, size_t length, EntityMask entityMask) +{ + DEFINE_STATIC_LOCAL(const String, ampReference, ("&")); + DEFINE_STATIC_LOCAL(const String, ltReference, ("<")); + DEFINE_STATIC_LOCAL(const String, gtReference, (">")); + DEFINE_STATIC_LOCAL(const String, quotReference, (""")); + DEFINE_STATIC_LOCAL(const String, nbspReference, (" ")); + + static const EntityDescription entityMaps[] = { + { '&', ampReference, EntityAmp }, + { '<', ltReference, EntityLt }, + { '>', gtReference, EntityGt }, + { '"', quotReference, EntityQuot }, + { noBreakSpace, nbspReference, EntityNbsp }, + }; + + size_t positionAfterLastEntity = 0; + for (size_t i = 0; i < length; i++) { + for (size_t m = 0; m < sizeof(entityMaps) / sizeof(EntityDescription); m++) { + if (content[i] == entityMaps[m].entity && entityMaps[m].mask & entityMask) { + out.append(content + positionAfterLastEntity, i - positionAfterLastEntity); + append(out, entityMaps[m].reference); + positionAfterLastEntity = i + 1; + break; + } + } + } + out.append(content + positionAfterLastEntity, length - positionAfterLastEntity); +} + +MarkupAccumulator::MarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs shouldResolveURLs, const Range* range) + : m_nodes(nodes) + , m_range(range) + , m_shouldResolveURLs(shouldResolveURLs) +{ +} + +MarkupAccumulator::~MarkupAccumulator() +{ +} + +String MarkupAccumulator::serializeNodes(Node* node, Node* nodeToSkip, EChildrenOnly childrenOnly) +{ + Vector<UChar> out; + serializeNodesWithNamespaces(node, nodeToSkip, childrenOnly, 0); + out.reserveInitialCapacity(length()); + concatenateMarkup(out); + return String::adopt(out); +} + +void MarkupAccumulator::serializeNodesWithNamespaces(Node* node, Node* nodeToSkip, EChildrenOnly childrenOnly, const Namespaces* namespaces) +{ + if (node == nodeToSkip) + return; + + Namespaces namespaceHash; + if (namespaces) + namespaceHash = *namespaces; + + if (!childrenOnly) + appendStartTag(node, &namespaceHash); + + if (!(node->document()->isHTMLDocument() && elementCannotHaveEndTag(node))) { + for (Node* current = node->firstChild(); current; current = current->nextSibling()) + serializeNodesWithNamespaces(current, nodeToSkip, IncludeNode, &namespaceHash); + } + + if (!childrenOnly) + appendEndTag(node); +} + +void MarkupAccumulator::appendString(const String& string) +{ + m_succeedingMarkup.append(string); +} + +void MarkupAccumulator::appendStartTag(Node* node, Namespaces* namespaces) +{ + Vector<UChar> markup; + appendStartMarkup(markup, node, namespaces); + appendString(String::adopt(markup)); + if (m_nodes) + m_nodes->append(node); +} + +void MarkupAccumulator::appendEndTag(Node* node) +{ + Vector<UChar> markup; + appendEndMarkup(markup, node); + appendString(String::adopt(markup)); +} + +size_t MarkupAccumulator::totalLength(const Vector<String>& strings) +{ + size_t length = 0; + for (size_t i = 0; i < strings.size(); ++i) + length += strings[i].length(); + return length; +} + +// FIXME: This is a very inefficient way of accumulating the markup. +// We're converting results of appendStartMarkup and appendEndMarkup from Vector<UChar> to String +// and then back to Vector<UChar> and again to String here. +void MarkupAccumulator::concatenateMarkup(Vector<UChar>& out) +{ + for (size_t i = 0; i < m_succeedingMarkup.size(); ++i) + append(out, m_succeedingMarkup[i]); +} + +void MarkupAccumulator::appendAttributeValue(Vector<UChar>& result, const String& attribute, bool documentIsHTML) +{ + appendCharactersReplacingEntities(result, attribute.characters(), attribute.length(), + documentIsHTML ? EntityMaskInHTMLAttributeValue : EntityMaskInAttributeValue); +} + +void MarkupAccumulator::appendQuotedURLAttributeValue(Vector<UChar>& result, const String& urlString) +{ + UChar quoteChar = '\"'; + String strippedURLString = urlString.stripWhiteSpace(); + if (protocolIsJavaScript(strippedURLString)) { + // minimal escaping for javascript urls + if (strippedURLString.contains('"')) { + if (strippedURLString.contains('\'')) + strippedURLString.replace('\"', """); + else + quoteChar = '\''; + } + result.append(quoteChar); + append(result, strippedURLString); + result.append(quoteChar); + return; + } + + // FIXME: This does not fully match other browsers. Firefox percent-escapes non-ASCII characters for innerHTML. + result.append(quoteChar); + appendAttributeValue(result, urlString, false); + result.append(quoteChar); +} + +void MarkupAccumulator::appendNodeValue(Vector<UChar>& out, const Node* node, const Range* range, EntityMask entityMask) +{ + String str = node->nodeValue(); + const UChar* characters = str.characters(); + size_t length = str.length(); + + if (range) { + ExceptionCode ec; + if (node == range->endContainer(ec)) + length = range->endOffset(ec); + if (node == range->startContainer(ec)) { + size_t start = range->startOffset(ec); + characters += start; + length -= start; + } + } + + appendCharactersReplacingEntities(out, characters, length, entityMask); +} + +bool MarkupAccumulator::shouldAddNamespaceElement(const Element* element) +{ + // Don't add namespace attribute if it is already defined for this elem. + const AtomicString& prefix = element->prefix(); + AtomicString attr = !prefix.isEmpty() ? "xmlns:" + prefix : "xmlns"; + return !element->hasAttribute(attr); +} + +bool MarkupAccumulator::shouldAddNamespaceAttribute(const Attribute& attribute, Namespaces& namespaces) +{ + namespaces.checkConsistency(); + + // Don't add namespace attributes twice + if (attribute.name() == XMLNSNames::xmlnsAttr) { + namespaces.set(emptyAtom.impl(), attribute.value().impl()); + return false; + } + + QualifiedName xmlnsPrefixAttr(xmlnsAtom, attribute.localName(), XMLNSNames::xmlnsNamespaceURI); + if (attribute.name() == xmlnsPrefixAttr) { + namespaces.set(attribute.localName().impl(), attribute.value().impl()); + return false; + } + + return true; +} + +void MarkupAccumulator::appendNamespace(Vector<UChar>& result, const AtomicString& prefix, const AtomicString& namespaceURI, Namespaces& namespaces) +{ + namespaces.checkConsistency(); + if (namespaceURI.isEmpty()) + return; + + // Use emptyAtoms's impl() for both null and empty strings since the HashMap can't handle 0 as a key + AtomicStringImpl* pre = prefix.isEmpty() ? emptyAtom.impl() : prefix.impl(); + AtomicStringImpl* foundNS = namespaces.get(pre); + if (foundNS != namespaceURI.impl()) { + namespaces.set(pre, namespaceURI.impl()); + result.append(' '); + append(result, xmlnsAtom.string()); + if (!prefix.isEmpty()) { + result.append(':'); + append(result, prefix); + } + + result.append('='); + result.append('"'); + appendAttributeValue(result, namespaceURI, false); + result.append('"'); + } +} + +EntityMask MarkupAccumulator::entityMaskForText(Text* text) const +{ + const QualifiedName* parentName = 0; + if (text->parentElement()) + parentName = &static_cast<Element*>(text->parentElement())->tagQName(); + + if (parentName && (*parentName == scriptTag || *parentName == styleTag || *parentName == xmpTag)) + return EntityMaskInCDATA; + + return text->document()->isHTMLDocument() ? EntityMaskInHTMLPCDATA : EntityMaskInPCDATA; +} + +void MarkupAccumulator::appendText(Vector<UChar>& out, Text* text) +{ + appendNodeValue(out, text, m_range, entityMaskForText(text)); +} + +void MarkupAccumulator::appendComment(Vector<UChar>& out, const String& comment) +{ + // FIXME: Comment content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "-->". + append(out, "<!--"); + append(out, comment); + append(out, "-->"); +} + +void MarkupAccumulator::appendDocumentType(Vector<UChar>& result, const DocumentType* n) +{ + if (n->name().isEmpty()) + return; + + append(result, "<!DOCTYPE "); + append(result, n->name()); + if (!n->publicId().isEmpty()) { + append(result, " PUBLIC \""); + append(result, n->publicId()); + append(result, "\""); + if (!n->systemId().isEmpty()) { + append(result, " \""); + append(result, n->systemId()); + append(result, "\""); + } + } else if (!n->systemId().isEmpty()) { + append(result, " SYSTEM \""); + append(result, n->systemId()); + append(result, "\""); + } + if (!n->internalSubset().isEmpty()) { + append(result, " ["); + append(result, n->internalSubset()); + append(result, "]"); + } + append(result, ">"); +} + +void MarkupAccumulator::appendProcessingInstruction(Vector<UChar>& out, const String& target, const String& data) +{ + // FIXME: PI data is not escaped, but XMLSerializer (and possibly other callers) this should raise an exception if it includes "?>". + append(out, "<?"); + append(out, target); + append(out, " "); + append(out, data); + append(out, "?>"); +} + +void MarkupAccumulator::appendElement(Vector<UChar>& out, Element* element, Namespaces* namespaces) +{ + appendOpenTag(out, element, namespaces); + + NamedNodeMap* attributes = element->attributes(); + unsigned length = attributes->length(); + for (unsigned int i = 0; i < length; i++) + appendAttribute(out, element, *attributes->attributeItem(i), namespaces); + + appendCloseTag(out, element); +} + +void MarkupAccumulator::appendOpenTag(Vector<UChar>& out, Element* element, Namespaces* namespaces) +{ + out.append('<'); + append(out, element->nodeNamePreservingCase()); + if (!element->document()->isHTMLDocument() && namespaces && shouldAddNamespaceElement(element)) + appendNamespace(out, element->prefix(), element->namespaceURI(), *namespaces); +} + +void MarkupAccumulator::appendCloseTag(Vector<UChar>& out, Element* element) +{ + if (shouldSelfClose(element)) { + if (element->isHTMLElement()) + out.append(' '); // XHTML 1.0 <-> HTML compatibility. + out.append('/'); + } + out.append('>'); +} + +void MarkupAccumulator::appendAttribute(Vector<UChar>& out, Element* element, const Attribute& attribute, Namespaces* namespaces) +{ + bool documentIsHTML = element->document()->isHTMLDocument(); + + out.append(' '); + + if (documentIsHTML) + append(out, attribute.name().localName()); + else + append(out, attribute.name().toString()); + + out.append('='); + + if (element->isURLAttribute(const_cast<Attribute*>(&attribute))) { + // We don't want to complete file:/// URLs because it may contain sensitive information + // about the user's system. + if (shouldResolveURLs() && !element->document()->url().isLocalFile()) + appendQuotedURLAttributeValue(out, element->document()->completeURL(attribute.value()).string()); + else + appendQuotedURLAttributeValue(out, attribute.value()); + } else { + out.append('\"'); + appendAttributeValue(out, attribute.value(), documentIsHTML); + out.append('\"'); + } + + if (!documentIsHTML && namespaces && shouldAddNamespaceAttribute(attribute, *namespaces)) + appendNamespace(out, attribute.prefix(), attribute.namespaceURI(), *namespaces); +} + +void MarkupAccumulator::appendCDATASection(Vector<UChar>& out, const String& section) +{ + // FIXME: CDATA content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "]]>". + append(out, "<![CDATA["); + append(out, section); + append(out, "]]>"); +} + +void MarkupAccumulator::appendStartMarkup(Vector<UChar>& result, const Node* node, Namespaces* namespaces) +{ + if (namespaces) + namespaces->checkConsistency(); + + switch (node->nodeType()) { + case Node::TEXT_NODE: + appendText(result, static_cast<Text*>(const_cast<Node*>(node))); + break; + case Node::COMMENT_NODE: + appendComment(result, static_cast<const Comment*>(node)->data()); + break; + case Node::DOCUMENT_NODE: + case Node::DOCUMENT_FRAGMENT_NODE: + break; + case Node::DOCUMENT_TYPE_NODE: + appendDocumentType(result, static_cast<const DocumentType*>(node)); + break; + case Node::PROCESSING_INSTRUCTION_NODE: + appendProcessingInstruction(result, static_cast<const ProcessingInstruction*>(node)->target(), static_cast<const ProcessingInstruction*>(node)->data()); + break; + case Node::ELEMENT_NODE: + appendElement(result, static_cast<Element*>(const_cast<Node*>(node)), namespaces); + break; + case Node::CDATA_SECTION_NODE: + appendCDATASection(result, static_cast<const CDATASection*>(node)->data()); + break; + case Node::ATTRIBUTE_NODE: + case Node::ENTITY_NODE: + case Node::ENTITY_REFERENCE_NODE: + case Node::NOTATION_NODE: + case Node::XPATH_NAMESPACE_NODE: + ASSERT_NOT_REACHED(); + break; + } +} + +// Rules of self-closure +// 1. No elements in HTML documents use the self-closing syntax. +// 2. Elements w/ children never self-close because they use a separate end tag. +// 3. HTML elements which do not have a "forbidden" end tag will close with a separate end tag. +// 4. Other elements self-close. +bool MarkupAccumulator::shouldSelfClose(const Node* node) +{ + if (node->document()->isHTMLDocument()) + return false; + if (node->hasChildNodes()) + return false; + if (node->isHTMLElement() && !elementCannotHaveEndTag(node)) + return false; + return true; +} + +bool MarkupAccumulator::elementCannotHaveEndTag(const Node* node) +{ + if (!node->isHTMLElement()) + return false; + + // FIXME: ieForbidsInsertHTML may not be the right function to call here + // ieForbidsInsertHTML is used to disallow setting innerHTML/outerHTML + // or createContextualFragment. It does not necessarily align with + // which elements should be serialized w/o end tags. + return static_cast<const HTMLElement*>(node)->ieForbidsInsertHTML(); +} + +void MarkupAccumulator::appendEndMarkup(Vector<UChar>& result, const Node* node) +{ + if (!node->isElementNode() || shouldSelfClose(node) || (!node->hasChildNodes() && elementCannotHaveEndTag(node))) + return; + + result.append('<'); + result.append('/'); + append(result, static_cast<const Element*>(node)->nodeNamePreservingCase()); + result.append('>'); +} + +} diff --git a/WebCore/editing/MarkupAccumulator.h b/WebCore/editing/MarkupAccumulator.h new file mode 100644 index 0000000..5a9c884 --- /dev/null +++ b/WebCore/editing/MarkupAccumulator.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MarkupAccumulator_h +#define MarkupAccumulator_h + +#include "PlatformString.h" +#include "markup.h" +#include <wtf/HashMap.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class Attribute; +class DocumentType; +class Element; +class Node; +class Range; + +typedef HashMap<AtomicStringImpl*, AtomicStringImpl*> Namespaces; + +enum EntityMask { + EntityAmp = 0x0001, + EntityLt = 0x0002, + EntityGt = 0x0004, + EntityQuot = 0x0008, + EntityNbsp = 0x0010, + + // Non-breaking space needs to be escaped in innerHTML for compatibility reason. See http://trac.webkit.org/changeset/32879 + // However, we cannot do this in a XML document because it does not have the entity reference defined (See the bug 19215). + EntityMaskInCDATA = 0, + EntityMaskInPCDATA = EntityAmp | EntityLt | EntityGt, + EntityMaskInHTMLPCDATA = EntityMaskInPCDATA | EntityNbsp, + EntityMaskInAttributeValue = EntityAmp | EntityLt | EntityGt | EntityQuot, + EntityMaskInHTMLAttributeValue = EntityMaskInAttributeValue | EntityNbsp, +}; + +struct EntityDescription { + UChar entity; + const String& reference; + EntityMask mask; +}; + +// FIXME: Noncopyable? +class MarkupAccumulator { +public: + MarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs shouldResolveURLs, const Range* range = 0); + virtual ~MarkupAccumulator(); + + String serializeNodes(Node* node, Node* nodeToSkip, EChildrenOnly childrenOnly); + +protected: + void appendString(const String&); + void appendStartTag(Node*, Namespaces* = 0); + void appendEndTag(Node*); + static size_t totalLength(const Vector<String>&); + size_t length() const { return totalLength(m_succeedingMarkup); } + void concatenateMarkup(Vector<UChar>& out); + void appendAttributeValue(Vector<UChar>& result, const String& attribute, bool documentIsHTML); + void appendQuotedURLAttributeValue(Vector<UChar>& result, const String& urlString); + void appendNodeValue(Vector<UChar>& out, const Node*, const Range*, EntityMask); + bool shouldAddNamespaceElement(const Element*); + bool shouldAddNamespaceAttribute(const Attribute&, Namespaces&); + void appendNamespace(Vector<UChar>& result, const AtomicString& prefix, const AtomicString& namespaceURI, Namespaces&); + EntityMask entityMaskForText(Text* text) const; + virtual void appendText(Vector<UChar>& out, Text*); + void appendComment(Vector<UChar>& out, const String& comment); + void appendDocumentType(Vector<UChar>& result, const DocumentType*); + void appendProcessingInstruction(Vector<UChar>& out, const String& target, const String& data); + virtual void appendElement(Vector<UChar>& out, Element*, Namespaces*); + void appendOpenTag(Vector<UChar>& out, Element* element, Namespaces*); + void appendCloseTag(Vector<UChar>& out, Element* element); + void appendAttribute(Vector<UChar>& out, Element* element, const Attribute&, Namespaces*); + void appendCDATASection(Vector<UChar>& out, const String& section); + void appendStartMarkup(Vector<UChar>& result, const Node*, Namespaces*); + bool shouldSelfClose(const Node*); + bool elementCannotHaveEndTag(const Node* node); + void appendEndMarkup(Vector<UChar>& result, const Node*); + + bool shouldResolveURLs() { return m_shouldResolveURLs == AbsoluteURLs; } + + Vector<Node*>* const m_nodes; + const Range* const m_range; + +private: + void serializeNodesWithNamespaces(Node*, Node* nodeToSkip, EChildrenOnly, const Namespaces*); + + Vector<String> m_succeedingMarkup; + const bool m_shouldResolveURLs; +}; + +// FIXME: This method should be integrated with MarkupAccumulator. +void appendCharactersReplacingEntities(Vector<UChar>& out, const UChar* content, size_t length, EntityMask entityMask); + +} + +#endif diff --git a/WebCore/editing/MergeIdenticalElementsCommand.cpp b/WebCore/editing/MergeIdenticalElementsCommand.cpp index f56f726..a1cb79f 100644 --- a/WebCore/editing/MergeIdenticalElementsCommand.cpp +++ b/WebCore/editing/MergeIdenticalElementsCommand.cpp @@ -67,7 +67,7 @@ void MergeIdenticalElementsCommand::doUnapply() RefPtr<Node> atChild = m_atChild.release(); - Node* parent = m_element2->parent(); + ContainerNode* parent = m_element2->parent(); if (!parent || !parent->isContentEditable()) return; diff --git a/WebCore/editing/RemoveNodeCommand.cpp b/WebCore/editing/RemoveNodeCommand.cpp index f6d6a4b..94e3e62 100644 --- a/WebCore/editing/RemoveNodeCommand.cpp +++ b/WebCore/editing/RemoveNodeCommand.cpp @@ -41,7 +41,7 @@ RemoveNodeCommand::RemoveNodeCommand(PassRefPtr<Node> node) void RemoveNodeCommand::doApply() { - Node* parent = m_node->parentNode(); + ContainerNode* parent = m_node->parentNode(); if (!parent || !parent->isContentEditable()) return; @@ -54,7 +54,7 @@ void RemoveNodeCommand::doApply() void RemoveNodeCommand::doUnapply() { - RefPtr<Node> parent = m_parent.release(); + RefPtr<ContainerNode> parent = m_parent.release(); RefPtr<Node> refChild = m_refChild.release(); if (!parent || !parent->isContentEditable()) return; diff --git a/WebCore/editing/RemoveNodeCommand.h b/WebCore/editing/RemoveNodeCommand.h index 14a798c..b803964 100644 --- a/WebCore/editing/RemoveNodeCommand.h +++ b/WebCore/editing/RemoveNodeCommand.h @@ -44,7 +44,7 @@ private: virtual void doUnapply(); RefPtr<Node> m_node; - RefPtr<Node> m_parent; + RefPtr<ContainerNode> m_parent; RefPtr<Node> m_refChild; }; diff --git a/WebCore/editing/ReplaceNodeWithSpanCommand.cpp b/WebCore/editing/ReplaceNodeWithSpanCommand.cpp index c3b6b89..7ab3aba 100644 --- a/WebCore/editing/ReplaceNodeWithSpanCommand.cpp +++ b/WebCore/editing/ReplaceNodeWithSpanCommand.cpp @@ -41,18 +41,18 @@ namespace WebCore { using namespace HTMLNames; -ReplaceNodeWithSpanCommand::ReplaceNodeWithSpanCommand(PassRefPtr<Node> node) - : CompositeEditCommand(node->document()) - , m_node(node) +ReplaceNodeWithSpanCommand::ReplaceNodeWithSpanCommand(PassRefPtr<HTMLElement> element) + : SimpleEditCommand(element->document()) + , m_elementToReplace(element) { - ASSERT(m_node); + ASSERT(m_elementToReplace); } -static void swapInNodePreservingAttributesAndChildren(Node* newNode, Node* nodeToReplace) +static void swapInNodePreservingAttributesAndChildren(HTMLElement* newNode, HTMLElement* nodeToReplace) { ASSERT(nodeToReplace->inDocument()); ExceptionCode ec = 0; - Node* parentNode = nodeToReplace->parentNode(); + ContainerNode* parentNode = nodeToReplace->parentNode(); parentNode->insertBefore(newNode, nodeToReplace, ec); ASSERT(!ec); @@ -71,18 +71,18 @@ static void swapInNodePreservingAttributesAndChildren(Node* newNode, Node* nodeT void ReplaceNodeWithSpanCommand::doApply() { - if (!m_node->inDocument()) + if (!m_elementToReplace->inDocument()) return; if (!m_spanElement) - m_spanElement = createHTMLElement(m_node->document(), spanTag); - swapInNodePreservingAttributesAndChildren(m_spanElement.get(), m_node.get()); + m_spanElement = createHTMLElement(m_elementToReplace->document(), spanTag); + swapInNodePreservingAttributesAndChildren(m_spanElement.get(), m_elementToReplace.get()); } void ReplaceNodeWithSpanCommand::doUnapply() { if (!m_spanElement->inDocument()) return; - swapInNodePreservingAttributesAndChildren(m_node.get(), m_spanElement.get()); + swapInNodePreservingAttributesAndChildren(m_elementToReplace.get(), m_spanElement.get()); } } // namespace WebCore diff --git a/WebCore/editing/ReplaceNodeWithSpanCommand.h b/WebCore/editing/ReplaceNodeWithSpanCommand.h index 7b375b6..0154f29 100644 --- a/WebCore/editing/ReplaceNodeWithSpanCommand.h +++ b/WebCore/editing/ReplaceNodeWithSpanCommand.h @@ -37,23 +37,23 @@ namespace WebCore { class HTMLElement; -// More accurately, this is ReplaceNodeWithSpanPreservingChildrenAndAttributesCommand -class ReplaceNodeWithSpanCommand : public CompositeEditCommand { +// More accurately, this is ReplaceElementWithSpanPreservingChildrenAndAttributesCommand +class ReplaceNodeWithSpanCommand : public SimpleEditCommand { public: - static PassRefPtr<ReplaceNodeWithSpanCommand> create(PassRefPtr<Node> node) + static PassRefPtr<ReplaceNodeWithSpanCommand> create(PassRefPtr<HTMLElement> element) { - return adoptRef(new ReplaceNodeWithSpanCommand(node)); + return adoptRef(new ReplaceNodeWithSpanCommand(element)); } HTMLElement* spanElement() { return m_spanElement.get(); } private: - ReplaceNodeWithSpanCommand(PassRefPtr<Node>); + ReplaceNodeWithSpanCommand(PassRefPtr<HTMLElement>); virtual void doApply(); virtual void doUnapply(); - RefPtr<Node> m_node; + RefPtr<HTMLElement> m_elementToReplace; RefPtr<HTMLElement> m_spanElement; }; diff --git a/WebCore/editing/ReplaceSelectionCommand.cpp b/WebCore/editing/ReplaceSelectionCommand.cpp index 49bdaca..056ab70 100644 --- a/WebCore/editing/ReplaceSelectionCommand.cpp +++ b/WebCore/editing/ReplaceSelectionCommand.cpp @@ -76,9 +76,9 @@ public: void removeNodePreservingChildren(Node*); private: - PassRefPtr<Node> insertFragmentForTestRendering(Node* context); + PassRefPtr<StyledElement> insertFragmentForTestRendering(Node* context); void removeUnrenderedNodes(Node*); - void restoreTestRenderingNodesToFragment(Node*); + void restoreTestRenderingNodesToFragment(StyledElement*); void removeInterchangeNodes(Node*); void insertNodeBefore(PassRefPtr<Node> node, Node* refNode); @@ -150,7 +150,7 @@ ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* f } Node* styleNode = selection.base().node(); - RefPtr<Node> holder = insertFragmentForTestRendering(styleNode); + RefPtr<StyledElement> holder = insertFragmentForTestRendering(styleNode); RefPtr<Range> range = VisibleSelection::selectionFromContentsOfNode(holder.get()).toNormalizedRange(); String text = plainText(range.get()); @@ -208,7 +208,7 @@ void ReplacementFragment::removeNode(PassRefPtr<Node> node) if (!node) return; - Node *parent = node->parentNode(); + ContainerNode* parent = node->parentNode(); if (!parent) return; @@ -222,7 +222,7 @@ void ReplacementFragment::insertNodeBefore(PassRefPtr<Node> node, Node* refNode) if (!node || !refNode) return; - Node* parent = refNode->parentNode(); + ContainerNode* parent = refNode->parentNode(); if (!parent) return; @@ -231,9 +231,9 @@ void ReplacementFragment::insertNodeBefore(PassRefPtr<Node> node, Node* refNode) ASSERT(ec == 0); } -PassRefPtr<Node> ReplacementFragment::insertFragmentForTestRendering(Node* context) +PassRefPtr<StyledElement> ReplacementFragment::insertFragmentForTestRendering(Node* context) { - Node* body = m_document->body(); + HTMLElement* body = m_document->body(); if (!body) return 0; @@ -266,7 +266,7 @@ PassRefPtr<Node> ReplacementFragment::insertFragmentForTestRendering(Node* conte return holder.release(); } -void ReplacementFragment::restoreTestRenderingNodesToFragment(Node *holder) +void ReplacementFragment::restoreTestRenderingNodesToFragment(StyledElement* holder) { if (!holder) return; @@ -628,7 +628,7 @@ void ReplaceSelectionCommand::handleStyleSpans() return; RefPtr<CSSMutableStyleDeclaration> sourceDocumentStyle = static_cast<HTMLElement*>(sourceDocumentStyleSpan)->getInlineStyleDecl()->copy(); - Node* context = sourceDocumentStyleSpan->parentNode(); + ContainerNode* context = sourceDocumentStyleSpan->parentNode(); // If Mail wraps the fragment with a Paste as Quotation blockquote, or if you're pasting into a quoted region, // styles from blockquoteNode are allowed to override those from the source document, see <rdar://problem/4930986> and <rdar://problem/5089327>. @@ -710,13 +710,15 @@ void ReplaceSelectionCommand::copyStyleToChildren(Node* parentNode, const CSSMut for (Node* childNode = parentNode->firstChild(); childNode; childNode = childNode->nextSibling()) { if (childNode->isTextNode() || !isBlock(childNode) || childNode->hasTagName(preTag)) { // In this case, put a span tag around the child node. - RefPtr<Node> newSpan = parentNode->cloneNode(false); - setNodeAttribute(static_cast<Element*>(newSpan.get()), styleAttr, parentStyle->cssText()); + RefPtr<Node> newNode = parentNode->cloneNode(false); + ASSERT(newNode->hasTagName(spanTag)); + HTMLElement* newSpan = static_cast<HTMLElement*>(newNode.get()); + setNodeAttribute(newSpan, styleAttr, parentStyle->cssText()); insertNodeAfter(newSpan, childNode); ExceptionCode ec = 0; newSpan->appendChild(childNode, ec); ASSERT(!ec); - childNode = newSpan.get(); + childNode = newSpan; } else if (childNode->isHTMLElement()) { // Copy the style attribute and merge them into the child node. We don't want to override // existing styles, so don't clobber on merge. @@ -777,6 +779,21 @@ void ReplaceSelectionCommand::mergeEndIfNeeded() } } +static Node* enclosingInline(Node* node) +{ + while (ContainerNode* parent = node->parentNode()) { + if (parent->isBlockFlow() || parent->hasTagName(bodyTag)) + return node; + // Stop if any previous sibling is a block. + for (Node* sibling = node->previousSibling(); sibling; sibling = sibling->previousSibling()) { + if (sibling->isBlockFlow()) + return node; + } + node = parent; + } + return node; +} + void ReplaceSelectionCommand::doApply() { VisibleSelection selection = endingSelection(); @@ -996,7 +1013,7 @@ void ReplaceSelectionCommand::doApply() // but our destination node is inside an inline that is the last in the block. // We insert a placeholder before the newly inserted content to avoid being merged into the inline. Node* destinationNode = destination.deepEquivalent().node(); - if (m_shouldMergeEnd && destinationNode != destinationNode->enclosingInlineElement() && destinationNode->enclosingInlineElement()->nextSibling()) + if (m_shouldMergeEnd && destinationNode != enclosingInline(destinationNode) && enclosingInline(destinationNode)->nextSibling()) insertNodeBefore(createBreakElement(document()), refNode.get()); // Merging the the first paragraph of inserted content with the content that came @@ -1211,7 +1228,7 @@ Node* ReplaceSelectionCommand::insertAsListItems(PassRefPtr<Node> listElement, N while (RefPtr<Node> listItem = listElement->firstChild()) { ExceptionCode ec = 0; - listElement->removeChild(listItem.get(), ec); + toContainerNode(listElement.get())->removeChild(listItem.get(), ec); ASSERT(!ec); if (isStart || isMiddle) insertNodeBefore(listItem, lastNode); diff --git a/WebCore/editing/SelectionController.cpp b/WebCore/editing/SelectionController.cpp index c9295d3..6f25c86 100644 --- a/WebCore/editing/SelectionController.cpp +++ b/WebCore/editing/SelectionController.cpp @@ -1085,7 +1085,7 @@ void SelectionController::paintCaret(GraphicsContext* context, int tx, int ty, c return; Color caretColor = Color::black; - ColorSpace colorSpace = DeviceColorSpace; + ColorSpace colorSpace = ColorSpaceDeviceRGB; Element* element = rootEditableElement(); if (element && element->renderer()) { caretColor = element->renderer()->style()->visitedDependentColor(CSSPropertyColor); @@ -1222,11 +1222,10 @@ void SelectionController::selectFrameElementInParentIfFullySelected() return; // Get to the <iframe> or <frame> (or even <object>) element in the parent frame. - Document* doc = m_frame->document(); - Element* ownerElement = doc->ownerElement(); + Element* ownerElement = m_frame->document()->ownerElement(); if (!ownerElement) return; - Node* ownerElementParent = ownerElement->parentNode(); + ContainerNode* ownerElementParent = ownerElement->parentNode(); if (!ownerElementParent) return; diff --git a/WebCore/editing/SplitElementCommand.cpp b/WebCore/editing/SplitElementCommand.cpp index 5047205..888c45f 100644 --- a/WebCore/editing/SplitElementCommand.cpp +++ b/WebCore/editing/SplitElementCommand.cpp @@ -53,7 +53,7 @@ void SplitElementCommand::executeApply() ExceptionCode ec = 0; - Node* parent = m_element2->parentNode(); + ContainerNode* parent = m_element2->parentNode(); if (!parent || !parent->isContentEditable()) return; parent->insertBefore(m_element1.get(), m_element2.get(), ec); diff --git a/WebCore/editing/SplitTextNodeCommand.cpp b/WebCore/editing/SplitTextNodeCommand.cpp index 1f38902..6c414f9 100644 --- a/WebCore/editing/SplitTextNodeCommand.cpp +++ b/WebCore/editing/SplitTextNodeCommand.cpp @@ -49,7 +49,7 @@ SplitTextNodeCommand::SplitTextNodeCommand(PassRefPtr<Text> text, int offset) void SplitTextNodeCommand::doApply() { - Node* parent = m_text2->parentNode(); + ContainerNode* parent = m_text2->parentNode(); if (!parent || !parent->isContentEditable()) return; @@ -87,7 +87,7 @@ void SplitTextNodeCommand::doReapply() if (!m_text1 || !m_text2) return; - Node* parent = m_text2->parentNode(); + ContainerNode* parent = m_text2->parentNode(); if (!parent || !parent->isContentEditable()) return; diff --git a/WebCore/editing/TextIterator.cpp b/WebCore/editing/TextIterator.cpp index 0612986..a96268d 100644 --- a/WebCore/editing/TextIterator.cpp +++ b/WebCore/editing/TextIterator.cpp @@ -112,6 +112,10 @@ BitStack::BitStack() { } +BitStack::~BitStack() +{ +} + void BitStack::push(bool bit) { unsigned index = m_size / bitsInWord; @@ -150,9 +154,9 @@ unsigned BitStack::size() const // -------- -static inline Node* parentCrossingShadowBoundaries(Node* node) +static inline ContainerNode* parentCrossingShadowBoundaries(Node* node) { - if (Node* parent = node->parentNode()) + if (ContainerNode* parent = node->parentNode()) return parent; return node->shadowParentNode(); } @@ -336,6 +340,10 @@ TextIterator::TextIterator(const Range* r, TextIteratorBehavior behavior) advance(); } +TextIterator::~TextIterator() +{ +} + void TextIterator::advance() { // reset the run information @@ -1456,6 +1464,10 @@ WordAwareIterator::WordAwareIterator(const Range* r) advance(); // get in position over the first chunk of text } +WordAwareIterator::~WordAwareIterator() +{ +} + // We're always in one of these modes: // - The current chunk in the text iterator is our current chunk // (typically its a piece of whitespace, or text that ended with whitespace) diff --git a/WebCore/editing/TextIterator.h b/WebCore/editing/TextIterator.h index 7fd87bd..1bd8828 100644 --- a/WebCore/editing/TextIterator.h +++ b/WebCore/editing/TextIterator.h @@ -64,6 +64,7 @@ PassRefPtr<Range> findPlainText(const Range*, const String&, bool forward, bool class BitStack { public: BitStack(); + ~BitStack(); void push(bool); void pop(); @@ -83,6 +84,7 @@ private: class TextIterator { public: TextIterator(); + ~TextIterator(); explicit TextIterator(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior); bool atEnd() const { return !m_positionNode; } @@ -291,6 +293,7 @@ class WordAwareIterator { public: WordAwareIterator(); explicit WordAwareIterator(const Range*); + ~WordAwareIterator(); bool atEnd() const { return !m_didLookAhead && m_textIterator.atEnd(); } void advance(); diff --git a/WebCore/editing/VisiblePosition.cpp b/WebCore/editing/VisiblePosition.cpp index 1b4a514..760c68c 100644 --- a/WebCore/editing/VisiblePosition.cpp +++ b/WebCore/editing/VisiblePosition.cpp @@ -123,9 +123,9 @@ Position VisiblePosition::leftVisuallyDistinctCandidate() const while (true) { if ((renderer->isReplaced() || renderer->isBR()) && offset == box->caretRightmostOffset()) - return box->direction() == LTR ? previousVisuallyDistinctCandidate(m_deepPosition) : nextVisuallyDistinctCandidate(m_deepPosition); + return box->isLeftToRightDirection() ? previousVisuallyDistinctCandidate(m_deepPosition) : nextVisuallyDistinctCandidate(m_deepPosition); - offset = box->direction() == LTR ? renderer->previousOffset(offset) : renderer->nextOffset(offset); + offset = box->isLeftToRightDirection() ? renderer->previousOffset(offset) : renderer->nextOffset(offset); int caretMinOffset = box->caretMinOffset(); int caretMaxOffset = box->caretMaxOffset(); @@ -133,7 +133,7 @@ Position VisiblePosition::leftVisuallyDistinctCandidate() const if (offset > caretMinOffset && offset < caretMaxOffset) break; - if (box->direction() == LTR ? offset < caretMinOffset : offset > caretMaxOffset) { + if (box->isLeftToRightDirection() ? offset < caretMinOffset : offset > caretMaxOffset) { // Overshot to the left. InlineBox* prevBox = box->prevLeafChild(); if (!prevBox) @@ -259,9 +259,9 @@ Position VisiblePosition::rightVisuallyDistinctCandidate() const while (true) { if ((renderer->isReplaced() || renderer->isBR()) && offset == box->caretLeftmostOffset()) - return box->direction() == LTR ? nextVisuallyDistinctCandidate(m_deepPosition) : previousVisuallyDistinctCandidate(m_deepPosition); + return box->isLeftToRightDirection() ? nextVisuallyDistinctCandidate(m_deepPosition) : previousVisuallyDistinctCandidate(m_deepPosition); - offset = box->direction() == LTR ? renderer->nextOffset(offset) : renderer->previousOffset(offset); + offset = box->isLeftToRightDirection() ? renderer->nextOffset(offset) : renderer->previousOffset(offset); int caretMinOffset = box->caretMinOffset(); int caretMaxOffset = box->caretMaxOffset(); @@ -269,7 +269,7 @@ Position VisiblePosition::rightVisuallyDistinctCandidate() const if (offset > caretMinOffset && offset < caretMaxOffset) break; - if (box->direction() == LTR ? offset > caretMaxOffset : offset < caretMinOffset) { + if (box->isLeftToRightDirection() ? offset > caretMaxOffset : offset < caretMinOffset) { // Overshot to the right. InlineBox* nextBox = box->nextLeafChild(); if (!nextBox) @@ -439,8 +439,13 @@ static Position canonicalizeCandidate(const Position& candidate) return candidate; } -Position VisiblePosition::canonicalPosition(const Position& position) +Position VisiblePosition::canonicalPosition(const Position& passedPosition) { + // The updateLayout call below can do so much that even the position passed + // in to us might get changed as a side effect. Specifically, there are code + // paths that pass selection endpoints, and updateLayout can change the selection. + Position position = passedPosition; + // FIXME (9535): Canonicalizing to the leftmost candidate means that if we're at a line wrap, we will // ask renderers to paint downstream carets for other renderers. // To fix this, we need to either a) add code to all paintCarets to pass the responsibility off to @@ -629,7 +634,7 @@ bool setEnd(Range *r, const VisiblePosition &visiblePosition) return code == 0; } -Node *enclosingBlockFlowElement(const VisiblePosition &visiblePosition) +Element* enclosingBlockFlowElement(const VisiblePosition &visiblePosition) { if (visiblePosition.isNull()) return NULL; diff --git a/WebCore/editing/VisiblePosition.h b/WebCore/editing/VisiblePosition.h index fe795a1..e649b68 100644 --- a/WebCore/editing/VisiblePosition.h +++ b/WebCore/editing/VisiblePosition.h @@ -136,7 +136,7 @@ bool setEnd(Range*, const VisiblePosition&); VisiblePosition startVisiblePosition(const Range*, EAffinity); VisiblePosition endVisiblePosition(const Range*, EAffinity); -Node *enclosingBlockFlowElement(const VisiblePosition&); +Element* enclosingBlockFlowElement(const VisiblePosition&); bool isFirstVisiblePositionInNode(const VisiblePosition&, const Node*); bool isLastVisiblePositionInNode(const VisiblePosition&, const Node*); diff --git a/WebCore/editing/chromium/EditorChromium.cpp b/WebCore/editing/chromium/EditorChromium.cpp index c938a7e..ca7f41a 100644 --- a/WebCore/editing/chromium/EditorChromium.cpp +++ b/WebCore/editing/chromium/EditorChromium.cpp @@ -39,7 +39,7 @@ namespace WebCore { PassRefPtr<Clipboard> Editor::newGeneralClipboard(ClipboardAccessPolicy policy, Frame* frame) { - return ClipboardChromium::create(Clipboard::CopyAndPaste, ChromiumDataObject::create(), policy, frame); + return ClipboardChromium::create(Clipboard::CopyAndPaste, policy, frame); } } // namespace WebCore diff --git a/WebCore/editing/gtk/SelectionControllerGtk.cpp b/WebCore/editing/gtk/SelectionControllerGtk.cpp index 9d52c1a..5626110 100644 --- a/WebCore/editing/gtk/SelectionControllerGtk.cpp +++ b/WebCore/editing/gtk/SelectionControllerGtk.cpp @@ -41,6 +41,9 @@ void SelectionController::notifyAccessibilityForSelectionChange() int offset; // Always report the events w.r.t. the non-linked unignored parent. (i.e. ignoreLinks == true) AccessibilityObject* object = objectAndOffsetUnignored(accessibilityObject, offset, true); + if (!object) + return; + AtkObject* wrapper = object->wrapper(); if (ATK_IS_TEXT(wrapper)) { g_signal_emit_by_name(wrapper, "text-caret-moved", offset); diff --git a/WebCore/editing/htmlediting.cpp b/WebCore/editing/htmlediting.cpp index 53b5d7e..f8f02b9 100644 --- a/WebCore/editing/htmlediting.cpp +++ b/WebCore/editing/htmlediting.cpp @@ -465,39 +465,6 @@ bool isSpecialElement(const Node *n) return false; } -// Checks if a string is a valid tag for the FormatBlockCommand function of execCommand. Expects lower case strings. -bool validBlockTag(const AtomicString& blockTag) -{ - if (blockTag.isEmpty()) - return false; - - DEFINE_STATIC_LOCAL(HashSet<AtomicString>, blockTags, ()); - if (blockTags.isEmpty()) { - blockTags.add(addressTag.localName()); - blockTags.add(articleTag.localName()); - blockTags.add(asideTag.localName()); - blockTags.add(blockquoteTag.localName()); - blockTags.add(ddTag.localName()); - blockTags.add(divTag.localName()); - blockTags.add(dlTag.localName()); - blockTags.add(dtTag.localName()); - blockTags.add(footerTag.localName()); - blockTags.add(h1Tag.localName()); - blockTags.add(h2Tag.localName()); - blockTags.add(h3Tag.localName()); - blockTags.add(h4Tag.localName()); - blockTags.add(h5Tag.localName()); - blockTags.add(h6Tag.localName()); - blockTags.add(headerTag.localName()); - blockTags.add(hgroupTag.localName()); - blockTags.add(navTag.localName()); - blockTags.add(pTag.localName()); - blockTags.add(preTag.localName()); - blockTags.add(sectionTag.localName()); - } - return blockTags.contains(blockTag); -} - static Node* firstInSpecialElement(const Position& pos) { // FIXME: This begins at pos.node(), which doesn't necessarily contain pos (suppose pos was [img, 0]). See <rdar://problem/5027702>. @@ -739,7 +706,7 @@ HTMLElement* enclosingList(Node* node) Node* root = highestEditableRoot(Position(node, 0)); - for (Node* n = node->parentNode(); n; n = n->parentNode()) { + for (ContainerNode* n = node->parentNode(); n; n = n->parentNode()) { if (n->hasTagName(ulTag) || n->hasTagName(olTag)) return static_cast<HTMLElement*>(n); if (n == root) @@ -1069,6 +1036,9 @@ bool lineBreakExistsAtPosition(const Position& position) if (position.anchorNode()->hasTagName(brTag) && position.atFirstEditingPositionForNode()) return true; + if (!position.anchorNode()->renderer()) + return false; + if (!position.anchorNode()->isTextNode() || !position.anchorNode()->renderer()->style()->preserveNewline()) return false; diff --git a/WebCore/editing/htmlediting.h b/WebCore/editing/htmlediting.h index aaf6ef2..1892357 100644 --- a/WebCore/editing/htmlediting.h +++ b/WebCore/editing/htmlediting.h @@ -228,8 +228,6 @@ VisibleSelection selectionForParagraphIteration(const VisibleSelection&); String stringWithRebalancedWhitespace(const String&, bool, bool); const String& nonBreakingSpaceString(); -bool validBlockTag(const AtomicString&); - } diff --git a/WebCore/editing/markup.cpp b/WebCore/editing/markup.cpp index 82499bf..75d567e 100644 --- a/WebCore/editing/markup.cpp +++ b/WebCore/editing/markup.cpp @@ -52,6 +52,7 @@ #include "InlineTextBox.h" #include "KURL.h" #include "Logging.h" +#include "MarkupAccumulator.h" #include "ProcessingInstruction.h" #include "Range.h" #include "TextIterator.h" @@ -91,456 +92,6 @@ private: QualifiedName m_name; String m_value; }; - -enum EntityMask { - EntityAmp = 0x0001, - EntityLt = 0x0002, - EntityGt = 0x0004, - EntityQuot = 0x0008, - EntityNbsp = 0x0010, - - // Non-breaking space needs to be escaped in innerHTML for compatibility reason. See http://trac.webkit.org/changeset/32879 - // However, we cannot do this in a XML document because it does not have the entity reference defined (See the bug 19215). - EntityMaskInCDATA = 0, - EntityMaskInPCDATA = EntityAmp | EntityLt | EntityGt, - EntityMaskInHTMLPCDATA = EntityMaskInPCDATA | EntityNbsp, - EntityMaskInAttributeValue = EntityAmp | EntityLt | EntityGt | EntityQuot, - EntityMaskInHTMLAttributeValue = EntityMaskInAttributeValue | EntityNbsp, -}; - -struct EntityDescription { - UChar entity; - const String& reference; - EntityMask mask; -}; - -static void appendCharactersReplacingEntities(Vector<UChar>& out, const UChar* content, size_t length, EntityMask entityMask) -{ - DEFINE_STATIC_LOCAL(const String, ampReference, ("&")); - DEFINE_STATIC_LOCAL(const String, ltReference, ("<")); - DEFINE_STATIC_LOCAL(const String, gtReference, (">")); - DEFINE_STATIC_LOCAL(const String, quotReference, (""")); - DEFINE_STATIC_LOCAL(const String, nbspReference, (" ")); - - static const EntityDescription entityMaps[] = { - { '&', ampReference, EntityAmp }, - { '<', ltReference, EntityLt }, - { '>', gtReference, EntityGt }, - { '"', quotReference, EntityQuot }, - { noBreakSpace, nbspReference, EntityNbsp }, - }; - - size_t positionAfterLastEntity = 0; - for (size_t i = 0; i < length; i++) { - for (size_t m = 0; m < sizeof(entityMaps) / sizeof(EntityDescription); m++) { - if (content[i] == entityMaps[m].entity && entityMaps[m].mask & entityMask) { - out.append(content + positionAfterLastEntity, i - positionAfterLastEntity); - append(out, entityMaps[m].reference); - positionAfterLastEntity = i + 1; - break; - } - } - } - out.append(content + positionAfterLastEntity, length - positionAfterLastEntity); -} - -typedef HashMap<AtomicStringImpl*, AtomicStringImpl*> Namespaces; - -class MarkupAccumulator { -public: - MarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs shouldResolveURLs, const Range* range = 0) - : m_nodes(nodes) - , m_range(range) - , m_shouldResolveURLs(shouldResolveURLs) - { - } - virtual ~MarkupAccumulator() {} - void appendString(const String&); - void appendStartTag(Node*, Namespaces* = 0); - void appendEndTag(Node*); - virtual String takeResults(); - -protected: - void appendAttributeValue(Vector<UChar>& result, const String& attribute, bool documentIsHTML); - void appendQuotedURLAttributeValue(Vector<UChar>& result, const String& urlString); - void appendNodeValue(Vector<UChar>& out, const Node*, const Range*, EntityMask); - bool shouldAddNamespaceElement(const Element*); - bool shouldAddNamespaceAttribute(const Attribute&, Namespaces&); - void appendNamespace(Vector<UChar>& result, const AtomicString& prefix, const AtomicString& namespaceURI, Namespaces&); - EntityMask entityMaskForText(Text* text) const; - virtual void appendText(Vector<UChar>& out, Text*); - void appendComment(Vector<UChar>& out, const String& comment); - void appendDocumentType(Vector<UChar>& result, const DocumentType*); - void appendProcessingInstruction(Vector<UChar>& out, const String& target, const String& data); - virtual void appendElement(Vector<UChar>& out, Element*, Namespaces*); - void appendOpenTag(Vector<UChar>& out, Element* element, Namespaces*); - void appendCloseTag(Vector<UChar>& out, Element* element); - void appendAttribute(Vector<UChar>& out, Element* element, const Attribute&, Namespaces*); - void appendCDATASection(Vector<UChar>& out, const String& section); - void appendStartMarkup(Vector<UChar>& result, const Node*, Namespaces*); - bool shouldSelfClose(const Node*); - void appendEndMarkup(Vector<UChar>& result, const Node*); - - bool shouldResolveURLs() { return m_shouldResolveURLs == AbsoluteURLs; } - - Vector<Node*>* const m_nodes; - const Range* const m_range; - Vector<String> m_succeedingMarkup; - -private: - const bool m_shouldResolveURLs; -}; - -void MarkupAccumulator::appendString(const String& string) -{ - m_succeedingMarkup.append(string); -} - -void MarkupAccumulator::appendStartTag(Node* node, Namespaces* namespaces) -{ - Vector<UChar> markup; - appendStartMarkup(markup, node, namespaces); - m_succeedingMarkup.append(String::adopt(markup)); - if (m_nodes) - m_nodes->append(node); -} - -void MarkupAccumulator::appendEndTag(Node* node) -{ - Vector<UChar> markup; - appendEndMarkup(markup, node); - m_succeedingMarkup.append(String::adopt(markup)); -} - -// FIXME: This is a very inefficient way of accumulating the markup. -// We're converting results of appendStartMarkup and appendEndMarkup from Vector<UChar> to String -// and then back to Vector<UChar> and again to String here. -String MarkupAccumulator::takeResults() -{ - size_t length = 0; - - size_t postCount = m_succeedingMarkup.size(); - for (size_t i = 0; i < postCount; ++i) - length += m_succeedingMarkup[i].length(); - - Vector<UChar> result; - result.reserveInitialCapacity(length); - - for (size_t i = 0; i < postCount; ++i) - append(result, m_succeedingMarkup[i]); - - return String::adopt(result); -} - -void MarkupAccumulator::appendAttributeValue(Vector<UChar>& result, const String& attribute, bool documentIsHTML) -{ - appendCharactersReplacingEntities(result, attribute.characters(), attribute.length(), - documentIsHTML ? EntityMaskInHTMLAttributeValue : EntityMaskInAttributeValue); -} - -void MarkupAccumulator::appendQuotedURLAttributeValue(Vector<UChar>& result, const String& urlString) -{ - UChar quoteChar = '\"'; - String strippedURLString = urlString.stripWhiteSpace(); - if (protocolIsJavaScript(strippedURLString)) { - // minimal escaping for javascript urls - if (strippedURLString.contains('"')) { - if (strippedURLString.contains('\'')) - strippedURLString.replace('\"', """); - else - quoteChar = '\''; - } - result.append(quoteChar); - append(result, strippedURLString); - result.append(quoteChar); - return; - } - - // FIXME: This does not fully match other browsers. Firefox percent-escapes non-ASCII characters for innerHTML. - result.append(quoteChar); - appendAttributeValue(result, urlString, false); - result.append(quoteChar); -} - -void MarkupAccumulator::appendNodeValue(Vector<UChar>& out, const Node* node, const Range* range, EntityMask entityMask) -{ - String str = node->nodeValue(); - const UChar* characters = str.characters(); - size_t length = str.length(); - - if (range) { - ExceptionCode ec; - if (node == range->endContainer(ec)) - length = range->endOffset(ec); - if (node == range->startContainer(ec)) { - size_t start = range->startOffset(ec); - characters += start; - length -= start; - } - } - - appendCharactersReplacingEntities(out, characters, length, entityMask); -} - -bool MarkupAccumulator::shouldAddNamespaceElement(const Element* element) -{ - // Don't add namespace attribute if it is already defined for this elem. - const AtomicString& prefix = element->prefix(); - AtomicString attr = !prefix.isEmpty() ? "xmlns:" + prefix : "xmlns"; - return !element->hasAttribute(attr); -} - -bool MarkupAccumulator::shouldAddNamespaceAttribute(const Attribute& attribute, Namespaces& namespaces) -{ - namespaces.checkConsistency(); - - // Don't add namespace attributes twice - if (attribute.name() == XMLNSNames::xmlnsAttr) { - namespaces.set(emptyAtom.impl(), attribute.value().impl()); - return false; - } - - QualifiedName xmlnsPrefixAttr(xmlnsAtom, attribute.localName(), XMLNSNames::xmlnsNamespaceURI); - if (attribute.name() == xmlnsPrefixAttr) { - namespaces.set(attribute.localName().impl(), attribute.value().impl()); - return false; - } - - return true; -} - -void MarkupAccumulator::appendNamespace(Vector<UChar>& result, const AtomicString& prefix, const AtomicString& namespaceURI, Namespaces& namespaces) -{ - namespaces.checkConsistency(); - if (namespaceURI.isEmpty()) - return; - - // Use emptyAtoms's impl() for both null and empty strings since the HashMap can't handle 0 as a key - AtomicStringImpl* pre = prefix.isEmpty() ? emptyAtom.impl() : prefix.impl(); - AtomicStringImpl* foundNS = namespaces.get(pre); - if (foundNS != namespaceURI.impl()) { - namespaces.set(pre, namespaceURI.impl()); - result.append(' '); - append(result, xmlnsAtom.string()); - if (!prefix.isEmpty()) { - result.append(':'); - append(result, prefix); - } - - result.append('='); - result.append('"'); - appendAttributeValue(result, namespaceURI, false); - result.append('"'); - } -} - -EntityMask MarkupAccumulator::entityMaskForText(Text* text) const -{ - const QualifiedName* parentName = 0; - if (text->parentElement()) - parentName = &static_cast<Element*>(text->parentElement())->tagQName(); - - if (parentName && (*parentName == scriptTag || *parentName == styleTag || *parentName == xmpTag)) - return EntityMaskInCDATA; - - return text->document()->isHTMLDocument() ? EntityMaskInHTMLPCDATA : EntityMaskInPCDATA; -} - -void MarkupAccumulator::appendText(Vector<UChar>& out, Text* text) -{ - appendNodeValue(out, text, m_range, entityMaskForText(text)); -} - -void MarkupAccumulator::appendComment(Vector<UChar>& out, const String& comment) -{ - // FIXME: Comment content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "-->". - append(out, "<!--"); - append(out, comment); - append(out, "-->"); -} - -void MarkupAccumulator::appendDocumentType(Vector<UChar>& result, const DocumentType* n) -{ - if (n->name().isEmpty()) - return; - - append(result, "<!DOCTYPE "); - append(result, n->name()); - if (!n->publicId().isEmpty()) { - append(result, " PUBLIC \""); - append(result, n->publicId()); - append(result, "\""); - if (!n->systemId().isEmpty()) { - append(result, " \""); - append(result, n->systemId()); - append(result, "\""); - } - } else if (!n->systemId().isEmpty()) { - append(result, " SYSTEM \""); - append(result, n->systemId()); - append(result, "\""); - } - if (!n->internalSubset().isEmpty()) { - append(result, " ["); - append(result, n->internalSubset()); - append(result, "]"); - } - append(result, ">"); -} - -void MarkupAccumulator::appendProcessingInstruction(Vector<UChar>& out, const String& target, const String& data) -{ - // FIXME: PI data is not escaped, but XMLSerializer (and possibly other callers) this should raise an exception if it includes "?>". - append(out, "<?"); - append(out, target); - append(out, " "); - append(out, data); - append(out, "?>"); -} - -void MarkupAccumulator::appendElement(Vector<UChar>& out, Element* element, Namespaces* namespaces) -{ - appendOpenTag(out, element, namespaces); - - NamedNodeMap* attributes = element->attributes(); - unsigned length = attributes->length(); - for (unsigned int i = 0; i < length; i++) - appendAttribute(out, element, *attributes->attributeItem(i), namespaces); - - appendCloseTag(out, element); -} - -void MarkupAccumulator::appendOpenTag(Vector<UChar>& out, Element* element, Namespaces* namespaces) -{ - out.append('<'); - append(out, element->nodeNamePreservingCase()); - if (!element->document()->isHTMLDocument() && namespaces && shouldAddNamespaceElement(element)) - appendNamespace(out, element->prefix(), element->namespaceURI(), *namespaces); -} - -void MarkupAccumulator::appendCloseTag(Vector<UChar>& out, Element* element) -{ - if (shouldSelfClose(element)) { - if (element->isHTMLElement()) - out.append(' '); // XHTML 1.0 <-> HTML compatibility. - out.append('/'); - } - out.append('>'); -} - -void MarkupAccumulator::appendAttribute(Vector<UChar>& out, Element* element, const Attribute& attribute, Namespaces* namespaces) -{ - bool documentIsHTML = element->document()->isHTMLDocument(); - - out.append(' '); - - if (documentIsHTML) - append(out, attribute.name().localName()); - else - append(out, attribute.name().toString()); - - out.append('='); - - if (element->isURLAttribute(const_cast<Attribute*>(&attribute))) { - // We don't want to complete file:/// URLs because it may contain sensitive information - // about the user's system. - if (shouldResolveURLs() && !element->document()->url().isLocalFile()) - appendQuotedURLAttributeValue(out, element->document()->completeURL(attribute.value()).string()); - else - appendQuotedURLAttributeValue(out, attribute.value()); - } else { - out.append('\"'); - appendAttributeValue(out, attribute.value(), documentIsHTML); - out.append('\"'); - } - - if (!documentIsHTML && namespaces && shouldAddNamespaceAttribute(attribute, *namespaces)) - appendNamespace(out, attribute.prefix(), attribute.namespaceURI(), *namespaces); -} - -void MarkupAccumulator::appendCDATASection(Vector<UChar>& out, const String& section) -{ - // FIXME: CDATA content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "]]>". - append(out, "<![CDATA["); - append(out, section); - append(out, "]]>"); -} - -void MarkupAccumulator::appendStartMarkup(Vector<UChar>& result, const Node* node, Namespaces* namespaces) -{ - if (namespaces) - namespaces->checkConsistency(); - - switch (node->nodeType()) { - case Node::TEXT_NODE: - appendText(result, static_cast<Text*>(const_cast<Node*>(node))); - break; - case Node::COMMENT_NODE: - appendComment(result, static_cast<const Comment*>(node)->data()); - break; - case Node::DOCUMENT_NODE: - case Node::DOCUMENT_FRAGMENT_NODE: - break; - case Node::DOCUMENT_TYPE_NODE: - appendDocumentType(result, static_cast<const DocumentType*>(node)); - break; - case Node::PROCESSING_INSTRUCTION_NODE: - appendProcessingInstruction(result, static_cast<const ProcessingInstruction*>(node)->target(), static_cast<const ProcessingInstruction*>(node)->data()); - break; - case Node::ELEMENT_NODE: - appendElement(result, static_cast<Element*>(const_cast<Node*>(node)), namespaces); - break; - case Node::CDATA_SECTION_NODE: - appendCDATASection(result, static_cast<const CDATASection*>(node)->data()); - break; - case Node::ATTRIBUTE_NODE: - case Node::ENTITY_NODE: - case Node::ENTITY_REFERENCE_NODE: - case Node::NOTATION_NODE: - case Node::XPATH_NAMESPACE_NODE: - ASSERT_NOT_REACHED(); - break; - } -} - -static inline bool elementCannotHaveEndTag(const Node *node) -{ - if (!node->isHTMLElement()) - return false; - - // FIXME: ieForbidsInsertHTML may not be the right function to call here - // ieForbidsInsertHTML is used to disallow setting innerHTML/outerHTML - // or createContextualFragment. It does not necessarily align with - // which elements should be serialized w/o end tags. - return static_cast<const HTMLElement*>(node)->ieForbidsInsertHTML(); -} - -// Rules of self-closure -// 1. No elements in HTML documents use the self-closing syntax. -// 2. Elements w/ children never self-close because they use a separate end tag. -// 3. HTML elements which do not have a "forbidden" end tag will close with a separate end tag. -// 4. Other elements self-close. -bool MarkupAccumulator::shouldSelfClose(const Node* node) -{ - if (node->document()->isHTMLDocument()) - return false; - if (node->hasChildNodes()) - return false; - if (node->isHTMLElement() && !elementCannotHaveEndTag(node)) - return false; - return true; -} - -void MarkupAccumulator::appendEndMarkup(Vector<UChar>& result, const Node* node) -{ - if (!node->isElementNode() || shouldSelfClose(node) || (!node->hasChildNodes() && elementCannotHaveEndTag(node))) - return; - - result.append('<'); - result.append('/'); - append(result, static_cast<const Element*>(node)->nodeNamePreservingCase()); - result.append('>'); -} static void completeURLs(Node* node, const String& baseURL) { @@ -576,11 +127,14 @@ public: , m_shouldAnnotate(shouldAnnotate) { } + + Node* serializeNodes(Node* startNode, Node* pastEnd); + void appendString(const String& s) { return MarkupAccumulator::appendString(s); } void wrapWithNode(Node*, bool convertBlocksToInlines = false, RangeFullySelectsNode = DoesFullySelectNode); void wrapWithStyleNode(CSSStyleDeclaration*, Document*, bool isBlock = false); String takeResults(); -protected: +private: virtual void appendText(Vector<UChar>& out, Text*); String renderedText(const Node*, const Range*); String stringValueForRange(const Node*, const Range*); @@ -590,7 +144,6 @@ protected: bool shouldAnnotate() { return m_shouldAnnotate == AnnotateForInterchange; } -private: Vector<String> m_reversedPrecedingMarkup; const EAnnotateForInterchange m_shouldAnnotate; }; @@ -623,29 +176,18 @@ void StyledMarkupAccumulator::wrapWithStyleNode(CSSStyleDeclaration* style, Docu openTag.append('\"'); openTag.append('>'); m_reversedPrecedingMarkup.append(String::adopt(openTag)); - m_succeedingMarkup.append(isBlock ? divClose : styleSpanClose); + appendString(isBlock ? divClose : styleSpanClose); } String StyledMarkupAccumulator::takeResults() { - size_t length = 0; - - size_t preCount = m_reversedPrecedingMarkup.size(); - for (size_t i = 0; i < preCount; ++i) - length += m_reversedPrecedingMarkup[i].length(); - - size_t postCount = m_succeedingMarkup.size(); - for (size_t i = 0; i < postCount; ++i) - length += m_succeedingMarkup[i].length(); - Vector<UChar> result; - result.reserveInitialCapacity(length); + result.reserveInitialCapacity(totalLength(m_reversedPrecedingMarkup) + length()); - for (size_t i = preCount; i > 0; --i) + for (size_t i = m_reversedPrecedingMarkup.size(); i > 0; --i) append(result, m_reversedPrecedingMarkup[i - 1]); - for (size_t i = 0; i < postCount; ++i) - append(result, m_succeedingMarkup[i]); + concatenateMarkup(result); return String::adopt(result); } @@ -779,7 +321,7 @@ void StyledMarkupAccumulator::removeExteriorStyles(CSSMutableStyleDeclaration* s style->removeProperty(CSSPropertyFloat); } -static Node* serializeNodes(StyledMarkupAccumulator& accumulator, Node* startNode, Node* pastEnd) +Node* StyledMarkupAccumulator::serializeNodes(Node* startNode, Node* pastEnd) { Vector<Node*> ancestorsToClose; Node* next; @@ -807,11 +349,11 @@ static Node* serializeNodes(StyledMarkupAccumulator& accumulator, Node* startNod next = pastEnd; } else { // Add the node to the markup if we're not skipping the descendants - accumulator.appendStartTag(n); + appendStartTag(n); // If node has no children, close the tag now. if (!n->childNodeCount()) { - accumulator.appendEndTag(n); + appendEndTag(n); lastClosed = n; } else { openedTag = true; @@ -828,22 +370,22 @@ static Node* serializeNodes(StyledMarkupAccumulator& accumulator, Node* startNod if (next != pastEnd && next->isDescendantOf(ancestor)) break; // Not at the end of the range, close ancestors up to sibling of next node. - accumulator.appendEndTag(ancestor); + appendEndTag(ancestor); lastClosed = ancestor; ancestorsToClose.removeLast(); } // Surround the currently accumulated markup with markup for ancestors we never opened as we leave the subtree(s) rooted at those ancestors. - Node* nextParent = next ? next->parentNode() : 0; + ContainerNode* nextParent = next ? next->parentNode() : 0; if (next != pastEnd && n != nextParent) { Node* lastAncestorClosedOrSelf = n->isDescendantOf(lastClosed) ? lastClosed : n; - for (Node *parent = lastAncestorClosedOrSelf->parent(); parent && parent != nextParent; parent = parent->parentNode()) { + for (ContainerNode* parent = lastAncestorClosedOrSelf->parent(); parent && parent != nextParent; parent = parent->parentNode()) { // All ancestors that aren't in the ancestorsToClose list should either be a) unrendered: if (!parent->renderer()) continue; // or b) ancestors that we never encountered during a pre-order traversal starting at startNode: ASSERT(startNode->isDescendantOf(parent)); - accumulator.wrapWithNode(parent); + wrapWithNode(parent); lastClosed = parent; } } @@ -861,7 +403,7 @@ static Node* ancestorToRetainStructureAndAppearance(Node* commonAncestor) return 0; if (commonAncestorBlock->hasTagName(tbodyTag) || commonAncestorBlock->hasTagName(trTag)) { - Node* table = commonAncestorBlock->parentNode(); + ContainerNode* table = commonAncestorBlock->parentNode(); while (table && !table->hasTagName(tableTag)) table = table->parentNode(); @@ -1067,11 +609,11 @@ String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterc Node* specialCommonAncestor = highestAncestorToWrapMarkup(updatedRange.get(), fullySelectedRoot, shouldAnnotate); - Node* lastClosed = serializeNodes(accumulator, startNode, pastEnd); + Node* lastClosed = accumulator.serializeNodes(startNode, pastEnd); if (specialCommonAncestor && lastClosed) { // Also include all of the ancestors of lastClosed up to this special ancestor. - for (Node* ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) { + for (ContainerNode* ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) { if (ancestor == fullySelectedRoot && !convertBlocksToInlines) { RefPtr<CSSMutableStyleDeclaration> fullySelectedRootStyle = styleFromMatchedRulesAndInlineDecl(fullySelectedRoot); @@ -1106,7 +648,7 @@ String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterc } // Add a wrapper span with the styles that all of the nodes in the markup inherit. - Node* parentOfLastClosed = lastClosed ? lastClosed->parentNode() : 0; + ContainerNode* parentOfLastClosed = lastClosed ? lastClosed->parentNode() : 0; if (parentOfLastClosed && parentOfLastClosed->renderer()) { RefPtr<CSSMutableStyleDeclaration> style = ApplyStyleCommand::editingStyleAtPosition(Position(parentOfLastClosed, 0)); @@ -1163,27 +705,6 @@ PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document* document, const return fragment.release(); } -static void serializeNodesWithNamespaces(MarkupAccumulator& accumulator, Node* node, Node* nodeToSkip, EChildrenOnly childrenOnly, const Namespaces* namespaces) -{ - if (node == nodeToSkip) - return; - - Namespaces namespaceHash; - if (namespaces) - namespaceHash = *namespaces; - - if (!childrenOnly) - accumulator.appendStartTag(node, &namespaceHash); - - if (!(node->document()->isHTMLDocument() && elementCannotHaveEndTag(node))) { - for (Node* current = node->firstChild(); current; current = current->nextSibling()) - serializeNodesWithNamespaces(accumulator, current, nodeToSkip, IncludeNode, &namespaceHash); - } - - if (!childrenOnly) - accumulator.appendEndTag(node); -} - String createMarkup(const Node* node, EChildrenOnly childrenOnly, Vector<Node*>* nodes, EAbsoluteURLs shouldResolveURLs) { if (!node) @@ -1197,8 +718,7 @@ String createMarkup(const Node* node, EChildrenOnly childrenOnly, Vector<Node*>* } MarkupAccumulator accumulator(nodes, shouldResolveURLs); - serializeNodesWithNamespaces(accumulator, const_cast<Node*>(node), deleteButtonContainerElement, childrenOnly, 0); - return accumulator.takeResults(); + return accumulator.serializeNodes(const_cast<Node*>(node), deleteButtonContainerElement, childrenOnly); } static void fillContainerFromString(ContainerNode* paragraph, const String& string) |