summaryrefslogtreecommitdiffstats
path: root/WebCore/editing
diff options
context:
space:
mode:
Diffstat (limited to 'WebCore/editing')
-rw-r--r--WebCore/editing/ApplyBlockElementCommand.cpp261
-rw-r--r--WebCore/editing/ApplyBlockElementCommand.h60
-rw-r--r--WebCore/editing/ApplyStyleCommand.cpp147
-rw-r--r--WebCore/editing/BreakBlockquoteCommand.cpp2
-rw-r--r--WebCore/editing/CompositeEditCommand.cpp19
-rw-r--r--WebCore/editing/CompositeEditCommand.h6
-rw-r--r--WebCore/editing/DeleteButtonController.cpp2
-rw-r--r--WebCore/editing/EditingAllInOne.cpp2
-rw-r--r--WebCore/editing/Editor.cpp74
-rw-r--r--WebCore/editing/Editor.h1
-rw-r--r--WebCore/editing/EditorCommand.cpp32
-rw-r--r--WebCore/editing/FormatBlockCommand.cpp165
-rw-r--r--WebCore/editing/FormatBlockCommand.h18
-rw-r--r--WebCore/editing/IndentOutdentCommand.cpp188
-rw-r--r--WebCore/editing/IndentOutdentCommand.h14
-rw-r--r--WebCore/editing/InsertNodeBeforeCommand.cpp2
-rw-r--r--WebCore/editing/JoinTextNodesCommand.cpp4
-rw-r--r--WebCore/editing/MarkupAccumulator.cpp465
-rw-r--r--WebCore/editing/MarkupAccumulator.h119
-rw-r--r--WebCore/editing/MergeIdenticalElementsCommand.cpp2
-rw-r--r--WebCore/editing/RemoveNodeCommand.cpp4
-rw-r--r--WebCore/editing/RemoveNodeCommand.h2
-rw-r--r--WebCore/editing/ReplaceNodeWithSpanCommand.cpp20
-rw-r--r--WebCore/editing/ReplaceNodeWithSpanCommand.h12
-rw-r--r--WebCore/editing/ReplaceSelectionCommand.cpp45
-rw-r--r--WebCore/editing/SelectionController.cpp7
-rw-r--r--WebCore/editing/SplitElementCommand.cpp2
-rw-r--r--WebCore/editing/SplitTextNodeCommand.cpp4
-rw-r--r--WebCore/editing/TextIterator.cpp16
-rw-r--r--WebCore/editing/TextIterator.h3
-rw-r--r--WebCore/editing/VisiblePosition.cpp21
-rw-r--r--WebCore/editing/VisiblePosition.h2
-rw-r--r--WebCore/editing/chromium/EditorChromium.cpp2
-rw-r--r--WebCore/editing/gtk/SelectionControllerGtk.cpp3
-rw-r--r--WebCore/editing/htmlediting.cpp38
-rw-r--r--WebCore/editing/htmlediting.h2
-rw-r--r--WebCore/editing/markup.cpp522
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, ("&amp;"));
+ DEFINE_STATIC_LOCAL(const String, ltReference, ("&lt;"));
+ DEFINE_STATIC_LOCAL(const String, gtReference, ("&gt;"));
+ DEFINE_STATIC_LOCAL(const String, quotReference, ("&quot;"));
+ DEFINE_STATIC_LOCAL(const String, nbspReference, ("&nbsp;"));
+
+ 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('\"', "&quot;");
+ 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, ("&amp;"));
- DEFINE_STATIC_LOCAL(const String, ltReference, ("&lt;"));
- DEFINE_STATIC_LOCAL(const String, gtReference, ("&gt;"));
- DEFINE_STATIC_LOCAL(const String, quotReference, ("&quot;"));
- DEFINE_STATIC_LOCAL(const String, nbspReference, ("&nbsp;"));
-
- 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('\"', "&quot;");
- 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)