summaryrefslogtreecommitdiffstats
path: root/WebCore/editing
diff options
context:
space:
mode:
Diffstat (limited to 'WebCore/editing')
-rw-r--r--WebCore/editing/ApplyStyleCommand.cpp70
-rw-r--r--WebCore/editing/ApplyStyleCommand.h7
-rw-r--r--WebCore/editing/DeleteButton.cpp12
-rw-r--r--WebCore/editing/DeleteSelectionCommand.cpp2
-rw-r--r--WebCore/editing/Editor.cpp113
-rw-r--r--WebCore/editing/Editor.h3
-rw-r--r--WebCore/editing/EditorCommand.cpp11
-rw-r--r--WebCore/editing/InsertLineBreakCommand.cpp2
-rw-r--r--WebCore/editing/InsertTextCommand.cpp2
-rw-r--r--WebCore/editing/RemoveFormatCommand.cpp2
-rw-r--r--WebCore/editing/ReplaceSelectionCommand.cpp4
-rw-r--r--WebCore/editing/SelectionController.cpp240
-rw-r--r--WebCore/editing/SelectionController.h40
-rw-r--r--WebCore/editing/TextIterator.cpp26
-rw-r--r--WebCore/editing/TextIterator.h23
-rw-r--r--WebCore/editing/TypingCommand.cpp6
-rw-r--r--WebCore/editing/mac/EditorMac.mm43
-rw-r--r--WebCore/editing/markup.cpp491
18 files changed, 725 insertions, 372 deletions
diff --git a/WebCore/editing/ApplyStyleCommand.cpp b/WebCore/editing/ApplyStyleCommand.cpp
index d104cbe..b2e93ec 100644
--- a/WebCore/editing/ApplyStyleCommand.cpp
+++ b/WebCore/editing/ApplyStyleCommand.cpp
@@ -464,7 +464,7 @@ PassRefPtr<CSSMutableStyleDeclaration> ApplyStyleCommand::editingStyleAtPosition
}
if (shouldIncludeTypingStyle == IncludeTypingStyle) {
- CSSMutableStyleDeclaration* typingStyle = pos.node()->document()->frame()->typingStyle();
+ CSSMutableStyleDeclaration* typingStyle = pos.node()->document()->frame()->selection()->typingStyle();
if (typingStyle)
style->merge(typingStyle);
}
@@ -1071,6 +1071,20 @@ void ApplyStyleCommand::fixRangeAndApplyInlineStyle(CSSMutableStyleDeclaration*
applyInlineStyleToNodeRange(style, startNode, pastEndNode);
}
+static bool containsNonEditableRegion(Node* node)
+{
+ if (!node->isContentEditable())
+ return true;
+
+ Node* sibling = node->traverseNextSibling();
+ for (Node* descendent = node->firstChild(); descendent && descendent != sibling; descendent = descendent->traverseNextNode()) {
+ if (!descendent->isContentEditable())
+ return true;
+ }
+
+ return false;
+}
+
void ApplyStyleCommand::applyInlineStyleToNodeRange(CSSMutableStyleDeclaration* style, Node* node, Node* pastEndNode)
{
for (Node* next; node && node != pastEndNode; node = next) {
@@ -1098,32 +1112,62 @@ void ApplyStyleCommand::applyInlineStyleToNodeRange(CSSMutableStyleDeclaration*
continue;
if (node->childNodeCount()) {
- if (editingIgnoresContent(node))
+ if (node->contains(pastEndNode) || containsNonEditableRegion(node) || !node->parentNode()->isContentEditable())
+ continue;
+ if (editingIgnoresContent(node)) {
next = node->traverseNextSibling();
- continue;
+ continue;
+ }
}
Node* runEnd = node;
Node* sibling = node->nextSibling();
- StyleChange startChange(style, Position(node, 0));
while (sibling && sibling != pastEndNode && !sibling->contains(pastEndNode)
&& (!isBlock(sibling) || sibling->hasTagName(brTag))
- && StyleChange(style, Position(sibling, 0)) == startChange) {
+ && !containsNonEditableRegion(sibling)) {
runEnd = sibling;
sibling = runEnd->nextSibling();
}
next = runEnd->traverseNextSibling();
+
+ if (!removeStyleFromRunBeforeApplyingStyle(style, node, runEnd))
+ continue;
addInlineStyleIfNeeded(style, node, runEnd, m_removeOnly ? DoNotAddStyledElement : AddStyledElement);
}
}
+bool ApplyStyleCommand::removeStyleFromRunBeforeApplyingStyle(CSSMutableStyleDeclaration* style, Node*& runStart, Node*& runEnd)
+{
+ Node* pastEndNode = runEnd->traverseNextSibling();
+ Node* next;
+ for (Node* node = runStart; node && node != pastEndNode; node = next) {
+ next = node->traverseNextNode();
+ if (!node->isHTMLElement())
+ continue;
+
+ Node* previousSibling = node->previousSibling();
+ Node* nextSibling = node->nextSibling();
+ Node* 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.
+ if (runStart == node)
+ runStart = previousSibling ? previousSibling->nextSibling() : parent->firstChild();
+ if (runEnd == node)
+ runEnd = nextSibling ? nextSibling->previousSibling() : parent->lastChild();
+ }
+ }
+
+ return true;
+}
+
bool ApplyStyleCommand::removeInlineStyleFromElement(CSSMutableStyleDeclaration* style, HTMLElement* element, InlineStyleRemovalMode mode)
{
ASSERT(style);
ASSERT(element);
if (m_styledInlineElement && element->hasTagName(m_styledInlineElement->tagQName())) {
- if (mode == RemoveAttributesAndElements)
+ if (mode != RemoveNone)
removeNodePreservingChildren(element);
return true;
}
@@ -1220,10 +1264,12 @@ bool ApplyStyleCommand::removeImplicitlyStyledElement(CSSMutableStyleDeclaration
else
mapValue = CSSPrimitiveValue::createIdentifier(equivalent.primitiveId).get();
- if (equivalent.isValueList && styleValue->isValueList() && static_cast<CSSValueList*>(styleValue.get())->hasValue(mapValue.get()))
- continue; // If CSS value assumes CSSValueList, then only skip if the value was present in style to apply.
- else if (mapValue && styleValue->cssText() == mapValue->cssText())
- continue; // If CSS value is primitive, then skip if they are equal.
+ if (mode != RemoveAlways) {
+ if (equivalent.isValueList && styleValue->isValueList() && static_cast<CSSValueList*>(styleValue.get())->hasValue(mapValue.get()))
+ continue; // If CSS value assumes CSSValueList, then only skip if the value was present in style to apply.
+ else if (mapValue && styleValue->cssText() == mapValue->cssText())
+ continue; // If CSS value is primitive, then skip if they are equal.
+ }
if (extractedStyle)
extractedStyle->setProperty(equivalent.propertyID, mapValue->cssText());
@@ -1342,7 +1388,7 @@ PassRefPtr<CSSMutableStyleDeclaration> ApplyStyleCommand::extractInlineStyleToPu
if (!style) {
style = CSSMutableStyleDeclaration::create();
- removeImplicitlyStyledElement(styleToApply, element, RemoveAttributesAndElements, style.get());
+ removeImplicitlyStyledElement(styleToApply, element, RemoveIfNeeded, style.get());
return style.release();
}
@@ -1364,7 +1410,7 @@ PassRefPtr<CSSMutableStyleDeclaration> ApplyStyleCommand::extractInlineStyleToPu
if (isSpanWithoutAttributesOrUnstyleStyleSpan(element))
removeNodePreservingChildren(element);
- removeImplicitlyStyledElement(styleToApply, element, RemoveAttributesAndElements, style.get());
+ removeImplicitlyStyledElement(styleToApply, element, RemoveIfNeeded, style.get());
return style.release();
}
diff --git a/WebCore/editing/ApplyStyleCommand.h b/WebCore/editing/ApplyStyleCommand.h
index 9f297e2..78f592e 100644
--- a/WebCore/editing/ApplyStyleCommand.h
+++ b/WebCore/editing/ApplyStyleCommand.h
@@ -42,7 +42,7 @@ enum ShouldIncludeTypingStyle {
class ApplyStyleCommand : public CompositeEditCommand {
public:
enum EPropertyLevel { PropertyDefault, ForceBlockProperties };
- enum InlineStyleRemovalMode { RemoveAttributesAndElements, RemoveNone };
+ enum InlineStyleRemovalMode { RemoveIfNeeded, RemoveAlways, RemoveNone };
enum EAddStyledElement { AddStyledElement, DoNotAddStyledElement };
static PassRefPtr<ApplyStyleCommand> create(Document* document, CSSStyleDeclaration* style, EditAction action = EditActionChangeAttributes, EPropertyLevel level = PropertyDefault)
@@ -71,11 +71,12 @@ private:
CSSMutableStyleDeclaration* style() const { return m_style.get(); }
// style-removal helpers
- bool removeInlineStyleFromElement(CSSMutableStyleDeclaration*, HTMLElement*, InlineStyleRemovalMode = RemoveAttributesAndElements);
+ bool removeStyleFromRunBeforeApplyingStyle(CSSMutableStyleDeclaration* style, Node*& runStart, Node*& runEnd);
+ bool removeInlineStyleFromElement(CSSMutableStyleDeclaration*, HTMLElement*, InlineStyleRemovalMode = RemoveIfNeeded);
inline bool shouldRemoveInlineStyleFromElement(CSSMutableStyleDeclaration* style, HTMLElement* element) {return removeInlineStyleFromElement(style, element, RemoveNone);}
bool removeImplicitlyStyledElement(CSSMutableStyleDeclaration*, HTMLElement*, InlineStyleRemovalMode, CSSMutableStyleDeclaration* extractedStyle = 0);
void replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement*&);
- bool removeCSSStyle(CSSMutableStyleDeclaration*, HTMLElement*, InlineStyleRemovalMode = RemoveAttributesAndElements);
+ bool removeCSSStyle(CSSMutableStyleDeclaration*, HTMLElement*, InlineStyleRemovalMode = RemoveIfNeeded);
HTMLElement* highestAncestorWithConflictingInlineStyle(CSSMutableStyleDeclaration*, Node*);
PassRefPtr<CSSMutableStyleDeclaration> extractInlineStyleToPushDown(CSSMutableStyleDeclaration*, Node*, bool isStyledElement);
void applyInlineStyleToPushDown(Node*, CSSMutableStyleDeclaration *style);
diff --git a/WebCore/editing/DeleteButton.cpp b/WebCore/editing/DeleteButton.cpp
index 91991cf..7f2fec0 100644
--- a/WebCore/editing/DeleteButton.cpp
+++ b/WebCore/editing/DeleteButton.cpp
@@ -50,14 +50,10 @@ PassRefPtr<DeleteButton> DeleteButton::create(Document* document)
void DeleteButton::defaultEventHandler(Event* event)
{
- // FIXME: Is it really import to check the type of the event?
- // Seems OK to respond to any event named click even if it does not have the correct type.
- if (event->isMouseEvent()) {
- if (event->type() == eventNames().clickEvent) {
- document()->frame()->editor()->deleteButtonController()->deleteTarget();
- event->setDefaultHandled();
- // FIXME: Shouldn't we return here instead of falling through?
- }
+ if (event->type() == eventNames().clickEvent) {
+ document()->frame()->editor()->deleteButtonController()->deleteTarget();
+ event->setDefaultHandled();
+ return;
}
HTMLImageElement::defaultEventHandler(event);
diff --git a/WebCore/editing/DeleteSelectionCommand.cpp b/WebCore/editing/DeleteSelectionCommand.cpp
index 70f0e88..4aa5c3c 100644
--- a/WebCore/editing/DeleteSelectionCommand.cpp
+++ b/WebCore/editing/DeleteSelectionCommand.cpp
@@ -714,7 +714,7 @@ void DeleteSelectionCommand::calculateTypingStyleAfterDelete()
// In this case if we start typing, the new characters should have the same style as the just deleted ones,
// but, if we change the selection, come back and start typing that style should be lost. Also see
// preserveTypingStyle() below.
- document()->frame()->setTypingStyle(m_typingStyle.get());
+ document()->frame()->selection()->setTypingStyle(m_typingStyle);
}
void DeleteSelectionCommand::clearTransientState()
diff --git a/WebCore/editing/Editor.cpp b/WebCore/editing/Editor.cpp
index c515bfc..24c1680 100644
--- a/WebCore/editing/Editor.cpp
+++ b/WebCore/editing/Editor.cpp
@@ -257,7 +257,7 @@ bool Editor::smartInsertDeleteEnabled()
bool Editor::canSmartCopyOrDelete()
{
- return client() && client()->smartInsertDeleteEnabled() && m_frame->selectionGranularity() == WordGranularity;
+ return client() && client()->smartInsertDeleteEnabled() && m_frame->selection()->granularity() == WordGranularity;
}
bool Editor::isSelectTrailingWhitespaceEnabled()
@@ -341,6 +341,7 @@ void Editor::pasteAsPlainTextWithPasteboard(Pasteboard* pasteboard)
pasteAsPlainText(text, canSmartReplaceWithPasteboard(pasteboard));
}
+#if !PLATFORM(MAC)
void Editor::pasteWithPasteboard(Pasteboard* pasteboard, bool allowPlainText)
{
RefPtr<Range> range = selectedRange();
@@ -349,6 +350,7 @@ void Editor::pasteWithPasteboard(Pasteboard* pasteboard, bool allowPlainText)
if (fragment && shouldInsertFragment(fragment, range, EditorInsertActionPasted))
pasteAsFragment(fragment, canSmartReplaceWithPasteboard(pasteboard), chosePlainText);
}
+#endif
bool Editor::canSmartReplaceWithPasteboard(Pasteboard* pasteboard)
{
@@ -359,10 +361,12 @@ bool Editor::shouldInsertFragment(PassRefPtr<DocumentFragment> fragment, PassRef
{
if (!client())
return false;
-
- Node* child = fragment->firstChild();
- if (child && fragment->lastChild() == child && child->isCharacterDataNode())
- return client()->shouldInsertText(static_cast<CharacterData*>(child)->data(), replacingDOMRange.get(), givenAction);
+
+ if (fragment) {
+ Node* child = fragment->firstChild();
+ if (child && fragment->lastChild() == child && child->isCharacterDataNode())
+ return client()->shouldInsertText(static_cast<CharacterData*>(child)->data(), replacingDOMRange.get(), givenAction);
+ }
return client()->shouldInsertNode(fragment.get(), replacingDOMRange.get(), givenAction);
}
@@ -551,7 +555,7 @@ WritingDirection Editor::textDirectionForSelection(bool& hasNestedOrMultipleEmbe
}
if (m_frame->selection()->isCaret()) {
- if (CSSMutableStyleDeclaration* typingStyle = m_frame->typingStyle()) {
+ if (CSSMutableStyleDeclaration* typingStyle = m_frame->selection()->typingStyle()) {
RefPtr<CSSValue> unicodeBidi = typingStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
if (unicodeBidi) {
ASSERT(unicodeBidi->isPrimitiveValue());
@@ -947,12 +951,6 @@ String Editor::selectionStartCSSPropertyValue(int propertyID)
String value = selectionStyle->getPropertyValue(propertyID);
- if (nodeToRemove) {
- ExceptionCode ec = 0;
- nodeToRemove->remove(ec);
- ASSERT(!ec);
- }
-
// If background color is transparent, traverse parent nodes until we hit a different value or document root
// Also, if the selection is a range, ignore the background color at the start of selection,
// and find the background color of the common ancestor.
@@ -969,11 +967,17 @@ String Editor::selectionStartCSSPropertyValue(int propertyID)
}
if (propertyID == CSSPropertyFontSize) {
- RefPtr<CSSValue> value = selectionStyle->getPropertyCSSValue(CSSPropertyFontSize);
- ASSERT(value->isPrimitiveValue());
- int fontPixelSize = static_cast<CSSPrimitiveValue*>(value.get())->getIntValue(CSSPrimitiveValue::CSS_PX);
+ RefPtr<CSSValue> cssValue = selectionStyle->getPropertyCSSValue(CSSPropertyFontSize);
+ ASSERT(cssValue->isPrimitiveValue());
+ int fontPixelSize = static_cast<CSSPrimitiveValue*>(cssValue.get())->getIntValue(CSSPrimitiveValue::CSS_PX);
int size = CSSStyleSelector::legacyFontSize(m_frame->document(), fontPixelSize, selectionStyle->useFixedFontDefaultSize());
- return String::number(size);
+ value = String::number(size);
+ }
+
+ if (nodeToRemove) {
+ ExceptionCode ec = 0;
+ nodeToRemove->remove(ec);
+ ASSERT(!ec);
}
return value;
@@ -1011,7 +1015,7 @@ void Editor::appliedEditing(PassRefPtr<EditCommand> cmd)
changeSelectionAfterCommand(newSelection, false, false);
if (!cmd->preservesTypingStyle())
- m_frame->setTypingStyle(0);
+ m_frame->selection()->clearTypingStyle();
// Command will be equal to last edit command only in the case of typing
if (m_lastEditCommand.get() == cmd)
@@ -1116,7 +1120,7 @@ bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectIn
// Reveal the current selection
if (Frame* editedFrame = document->frame())
if (Page* page = editedFrame->page())
- page->focusController()->focusedOrMainFrame()->revealSelection(ScrollAlignment::alignToEdgeIfNeeded);
+ page->focusController()->focusedOrMainFrame()->selection()->revealSelection(ScrollAlignment::alignToEdgeIfNeeded);
}
}
@@ -1193,8 +1197,6 @@ void Editor::copy()
didWriteSelectionToPasteboard();
}
-#if !PLATFORM(MAC)
-
void Editor::paste()
{
ASSERT(m_frame->document());
@@ -1211,8 +1213,6 @@ void Editor::paste()
loader->setAllowStaleResources(false);
}
-#endif
-
void Editor::pasteAsPlainText()
{
if (tryDHTMLPaste())
@@ -1451,9 +1451,7 @@ void Editor::toggleUnderline()
void Editor::setBaseWritingDirection(WritingDirection direction)
{
Node* focusedNode = frame()->document()->focusedNode();
- if (focusedNode && (focusedNode->hasTagName(textareaTag)
- || (focusedNode->hasTagName(inputTag) && (static_cast<HTMLInputElement*>(focusedNode)->inputType() == HTMLInputElement::TEXT
- || static_cast<HTMLInputElement*>(focusedNode)->inputType() == HTMLInputElement::SEARCH)))) {
+ if (focusedNode && (focusedNode->hasTagName(textareaTag) || (focusedNode->hasTagName(inputTag) && static_cast<HTMLInputElement*>(focusedNode)->isTextField()))) {
if (direction == NaturalWritingDirection)
return;
static_cast<HTMLElement*>(focusedNode)->setAttribute(dirAttr, direction == LeftToRightWritingDirection ? "ltr" : "rtl");
@@ -2132,7 +2130,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection)
// FIXME 4859190: This gets confused with doubled punctuation at the end of a paragraph
RefPtr<Range> badGrammarRange = TextIterator::subrange(grammarSearchRange.get(), grammarPhraseOffset + grammarDetail.location, grammarDetail.length);
frame()->selection()->setSelection(VisibleSelection(badGrammarRange.get(), SEL_DEFAULT_AFFINITY));
- frame()->revealSelection();
+ frame()->selection()->revealSelection();
client()->updateSpellingUIWithGrammarString(badGrammarPhrase, grammarDetail);
frame()->document()->markers()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, grammarDetail.userDescription);
@@ -2143,7 +2141,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection)
RefPtr<Range> misspellingRange = TextIterator::subrange(spellingSearchRange.get(), misspellingOffset, misspelledWord.length());
frame()->selection()->setSelection(VisibleSelection(misspellingRange.get(), DOWNSTREAM));
- frame()->revealSelection();
+ frame()->selection()->revealSelection();
client()->updateSpellingUIWithMisspelledWord(misspelledWord);
frame()->document()->markers()->addMarker(misspellingRange.get(), DocumentMarker::Spelling);
@@ -2435,7 +2433,7 @@ void Editor::markMisspellingsAfterTypingToPosition(const VisiblePosition &p)
if (!autocorrectedString.isEmpty()) {
VisibleSelection newSelection(misspellingRange.get(), DOWNSTREAM);
if (newSelection != frame()->selection()->selection()) {
- if (!frame()->shouldChangeSelection(newSelection))
+ if (!frame()->selection()->shouldChangeSelection(newSelection))
return;
frame()->selection()->setSelection(newSelection);
}
@@ -2709,7 +2707,7 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh
}
}
if (doReplacement && !shouldShowCorrectionPanel && selectionToReplace != m_frame->selection()->selection()) {
- if (m_frame->shouldChangeSelection(selectionToReplace)) {
+ if (m_frame->selection()->shouldChangeSelection(selectionToReplace)) {
m_frame->selection()->setSelection(selectionToReplace);
selectionChanged = true;
} else {
@@ -2854,6 +2852,15 @@ void Editor::handleCancelOperation()
#endif
}
+bool Editor::isShowingCorrectionPanel()
+{
+#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
+ if (client())
+ return client()->isShowingCorrectionPanel();
+#endif
+ return false;
+}
+
PassRefPtr<Range> Editor::rangeForPoint(const IntPoint& windowPoint)
{
Document* document = m_frame->documentAtPoint(windowPoint);
@@ -2875,7 +2882,7 @@ void Editor::revealSelectionAfterEditingOperation()
if (m_ignoreCompositionSelectionChange)
return;
- m_frame->revealSelection(ScrollAlignment::alignToEdgeIfNeeded);
+ m_frame->selection()->revealSelection(ScrollAlignment::alignToEdgeIfNeeded);
}
void Editor::setIgnoreCompositionSelectionChange(bool ignore)
@@ -2952,7 +2959,7 @@ void Editor::transpose()
// Select the two characters.
if (newSelection != m_frame->selection()->selection()) {
- if (!m_frame->shouldChangeSelection(newSelection))
+ if (!m_frame->selection()->shouldChangeSelection(newSelection))
return;
m_frame->selection()->setSelection(newSelection);
}
@@ -3125,7 +3132,7 @@ void Editor::changeSelectionAfterCommand(const VisibleSelection& newSelection, b
// The old selection can be invalid here and calling shouldChangeSelection can produce some strange calls.
// See <rdar://problem/5729315> Some shouldChangeSelectedDOMRange contain Ranges for selections that are no longer valid
bool selectionDidNotChangeDOMPosition = newSelection == m_frame->selection()->selection();
- if (selectionDidNotChangeDOMPosition || m_frame->shouldChangeSelection(newSelection))
+ if (selectionDidNotChangeDOMPosition || m_frame->selection()->shouldChangeSelection(newSelection))
m_frame->selection()->setSelection(newSelection, closeTyping, clearTypingStyle);
// Some editing operations change the selection visually without affecting its position within the DOM.
@@ -3196,18 +3203,18 @@ bool Editor::shouldChangeSelection(const VisibleSelection& oldSelection, const V
return client()->shouldChangeSelectedRange(oldSelection.toNormalizedRange().get(), newSelection.toNormalizedRange().get(), affinity, stillSelecting);
}
-void Editor::computeAndSetTypingStyle(CSSStyleDeclaration *style, EditAction editingAction)
+void Editor::computeAndSetTypingStyle(CSSStyleDeclaration* style, EditAction editingAction)
{
if (!style || !style->length()) {
- m_frame->clearTypingStyle();
+ m_frame->selection()->clearTypingStyle();
return;
}
// Calculate the current typing style.
RefPtr<CSSMutableStyleDeclaration> mutableStyle = style->makeMutable();
- if (m_frame->typingStyle()) {
- m_frame->typingStyle()->merge(mutableStyle.get());
- mutableStyle = m_frame->typingStyle();
+ if (m_frame->selection()->typingStyle()) {
+ m_frame->selection()->typingStyle()->merge(mutableStyle.get());
+ mutableStyle = m_frame->selection()->typingStyle();
}
RefPtr<CSSValue> unicodeBidi;
@@ -3236,7 +3243,7 @@ void Editor::computeAndSetTypingStyle(CSSStyleDeclaration *style, EditAction edi
applyCommand(ApplyStyleCommand::create(m_frame->document(), blockStyle.get(), editingAction));
// Set the remaining style as the typing style.
- m_frame->setTypingStyle(mutableStyle.get());
+ m_frame->selection()->setTypingStyle(mutableStyle.release());
}
PassRefPtr<CSSComputedStyleDeclaration> Editor::selectionComputedStyle(Node*& nodeToRemove) const
@@ -3264,10 +3271,10 @@ PassRefPtr<CSSComputedStyleDeclaration> Editor::selectionComputedStyle(Node*& no
RefPtr<Element> styleElement = element;
ExceptionCode ec = 0;
- if (m_frame->typingStyle()) {
+ if (m_frame->selection()->typingStyle()) {
styleElement = m_frame->document()->createElement(spanTag, false);
- styleElement->setAttribute(styleAttr, m_frame->typingStyle()->cssText().impl(), ec);
+ styleElement->setAttribute(styleAttr, m_frame->selection()->typingStyle()->cssText(), ec);
ASSERT(!ec);
styleElement->appendChild(m_frame->document()->createEditingTextNode(""), ec);
@@ -3368,13 +3375,13 @@ RenderStyle* Editor::styleForSelectionStart(Node *&nodeToRemove) const
if (!position.node())
return 0;
- if (!m_frame->typingStyle())
+ if (!m_frame->selection()->typingStyle())
return position.node()->renderer()->style();
RefPtr<Element> styleElement = m_frame->document()->createElement(spanTag, false);
ExceptionCode ec = 0;
- String styleText = m_frame->typingStyle()->cssText() + " display: inline";
+ String styleText = m_frame->selection()->typingStyle()->cssText() + " display: inline";
styleElement->setAttribute(styleAttr, styleText.impl(), ec);
ASSERT(!ec);
@@ -3471,7 +3478,7 @@ bool Editor::findString(const String& target, bool forward, bool caseFlag, bool
return false;
m_frame->selection()->setSelection(VisibleSelection(resultRange.get(), DOWNSTREAM));
- m_frame->revealSelection();
+ m_frame->selection()->revealSelection();
return true;
}
@@ -3591,4 +3598,24 @@ void Editor::respondToChangedSelection(const VisibleSelection& oldSelection, boo
respondToChangedSelection(oldSelection);
}
+bool Editor::selectionStartHasSpellingMarkerFor(int from, int length) const
+{
+ Node* node = m_frame->selection()->start().node();
+ if (!node || !node->renderer())
+ return false;
+ ASSERT(node->renderer()->isText());
+
+ unsigned int startOffset = static_cast<unsigned int>(from);
+ unsigned int endOffset = static_cast<unsigned int>(from + length);
+ Vector<DocumentMarker> markers = m_frame->document()->markers()->markersForNode(node);
+ for (size_t i = 0; i < markers.size(); ++i) {
+ DocumentMarker marker = markers[i];
+ if (marker.startOffset <= startOffset && endOffset <= marker.endOffset && marker.type == DocumentMarker::Spelling)
+ return true;
+ }
+
+ return false;
+}
+
+
} // namespace WebCore
diff --git a/WebCore/editing/Editor.h b/WebCore/editing/Editor.h
index 2c06cea..7a4a185 100644
--- a/WebCore/editing/Editor.h
+++ b/WebCore/editing/Editor.h
@@ -313,6 +313,7 @@ public:
void handleCancelOperation();
void startCorrectionPanelTimer();
void handleRejectedCorrection();
+ bool isShowingCorrectionPanel();
void pasteAsFragment(PassRefPtr<DocumentFragment>, bool smartReplace, bool matchStyle);
void pasteAsPlainText(const String&, bool smartReplace);
@@ -360,6 +361,8 @@ public:
NSWritingDirection baseWritingDirectionForSelectionStart() const;
#endif
+ bool selectionStartHasSpellingMarkerFor(int from, int length) const;
+
private:
Frame* m_frame;
OwnPtr<DeleteButtonController> m_deleteButtonController;
diff --git a/WebCore/editing/EditorCommand.cpp b/WebCore/editing/EditorCommand.cpp
index 4f53ae9..1b1f14f 100644
--- a/WebCore/editing/EditorCommand.cpp
+++ b/WebCore/editing/EditorCommand.cpp
@@ -322,7 +322,7 @@ static bool executeDelete(Frame* frame, Event*, EditorCommandSource source, cons
case CommandFromDOMWithUserInterface:
// If the current selection is a caret, delete the preceding character. IE performs forwardDelete, but we currently side with Firefox.
// Doesn't scroll to make the selection visible, or modify the kill ring (this time, siding with IE, not Firefox).
- TypingCommand::deleteKeyPressed(frame->document(), frame->selectionGranularity() == WordGranularity);
+ TypingCommand::deleteKeyPressed(frame->document(), frame->selection()->granularity() == WordGranularity);
return true;
}
ASSERT_NOT_REACHED();
@@ -1122,6 +1122,13 @@ static bool supportedPaste(Frame* frame, EditorCommandSource source)
return false;
}
+#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
+static bool supportedDismissCorrectionPanel(Frame* frame, EditorCommandSource source)
+{
+ return supportedFromMenuOrKeyBinding(frame, source) && frame->editor()->isShowingCorrectionPanel();
+}
+#endif
+
// Enabled functions
static bool enabled(Frame*, Event*, EditorCommandSource)
@@ -1467,7 +1474,7 @@ static const CommandMap& createCommandMap()
{ "Yank", { executeYank, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } },
{ "YankAndSelect", { executeYankAndSelect, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } },
#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
- { "CancelOperation", { executeCancelOperation, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } },
+ { "CancelOperation", { executeCancelOperation, supportedDismissCorrectionPanel, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } },
#endif
};
diff --git a/WebCore/editing/InsertLineBreakCommand.cpp b/WebCore/editing/InsertLineBreakCommand.cpp
index dbe4b39..8ac1167 100644
--- a/WebCore/editing/InsertLineBreakCommand.cpp
+++ b/WebCore/editing/InsertLineBreakCommand.cpp
@@ -165,7 +165,7 @@ void InsertLineBreakCommand::doApply()
// Handle the case where there is a typing style.
- CSSMutableStyleDeclaration* typingStyle = document()->frame()->typingStyle();
+ CSSMutableStyleDeclaration* typingStyle = document()->frame()->selection()->typingStyle();
if (typingStyle && typingStyle->length() > 0) {
// Apply the typing style to the inserted line break, so that if the selection
diff --git a/WebCore/editing/InsertTextCommand.cpp b/WebCore/editing/InsertTextCommand.cpp
index cfaf219..b6c8236 100644
--- a/WebCore/editing/InsertTextCommand.cpp
+++ b/WebCore/editing/InsertTextCommand.cpp
@@ -190,7 +190,7 @@ void InsertTextCommand::input(const String& text, bool selectInsertedText)
setEndingSelection(forcedEndingSelection);
// Handle the case where there is a typing style.
- CSSMutableStyleDeclaration* typingStyle = document()->frame()->typingStyle();
+ CSSMutableStyleDeclaration* typingStyle = document()->frame()->selection()->typingStyle();
RefPtr<CSSComputedStyleDeclaration> endingStyle = endPosition.computedStyle();
RefPtr<CSSValue> unicodeBidi;
RefPtr<CSSValue> direction;
diff --git a/WebCore/editing/RemoveFormatCommand.cpp b/WebCore/editing/RemoveFormatCommand.cpp
index 257172b..65f6008 100644
--- a/WebCore/editing/RemoveFormatCommand.cpp
+++ b/WebCore/editing/RemoveFormatCommand.cpp
@@ -78,7 +78,7 @@ void RemoveFormatCommand::doApply()
// Insert the content with the default style.
// See <rdar://problem/5794382> RemoveFormat doesn't always reset text alignment
- frame->setTypingStyle(defaultStyle.get());
+ frame->selection()->setTypingStyle(defaultStyle.release());
inputText(string, true);
}
diff --git a/WebCore/editing/ReplaceSelectionCommand.cpp b/WebCore/editing/ReplaceSelectionCommand.cpp
index 38f3ed2..49bdaca 100644
--- a/WebCore/editing/ReplaceSelectionCommand.cpp
+++ b/WebCore/editing/ReplaceSelectionCommand.cpp
@@ -898,7 +898,7 @@ void ReplaceSelectionCommand::doApply()
// FIXME: Can this wait until after the operation has been performed? There doesn't seem to be
// any work performed after this that queries or uses the typing style.
if (Frame* frame = document()->frame())
- frame->clearTypingStyle();
+ frame->selection()->clearTypingStyle();
bool handledStyleSpans = handleStyleSpansBeforeInsertion(fragment, insertionPos);
@@ -1062,7 +1062,7 @@ void ReplaceSelectionCommand::doApply()
if (m_smartReplace && currentRoot) {
// Disable smart replace for password fields.
Node* start = currentRoot->shadowAncestorNode();
- if (start->hasTagName(inputTag) && static_cast<HTMLInputElement*>(start)->inputType() == HTMLInputElement::PASSWORD)
+ if (start->hasTagName(inputTag) && static_cast<HTMLInputElement*>(start)->isPasswordField())
m_smartReplace = false;
}
if (m_smartReplace) {
diff --git a/WebCore/editing/SelectionController.cpp b/WebCore/editing/SelectionController.cpp
index 99b2224..c04fe25 100644
--- a/WebCore/editing/SelectionController.cpp
+++ b/WebCore/editing/SelectionController.cpp
@@ -29,6 +29,7 @@
#include "DeleteSelectionCommand.h"
#include "Document.h"
#include "Editor.h"
+#include "EditorClient.h"
#include "Element.h"
#include "EventHandler.h"
#include "ExceptionCode.h"
@@ -38,7 +39,8 @@
#include "FrameTree.h"
#include "FrameView.h"
#include "GraphicsContext.h"
-#include "HTMLFrameOwnerElement.h"
+#include "HTMLFormElement.h"
+#include "HTMLFrameElementBase.h"
#include "HTMLInputElement.h"
#include "HTMLNames.h"
#include "HitTestRequest.h"
@@ -46,8 +48,10 @@
#include "Page.h"
#include "Range.h"
#include "RenderLayer.h"
+#include "RenderTextControl.h"
#include "RenderTheme.h"
#include "RenderView.h"
+#include "RenderWidget.h"
#include "SecureTextInput.h"
#include "Settings.h"
#include "TextIterator.h"
@@ -107,7 +111,7 @@ void SelectionController::moveTo(const Position &base, const Position &extent, E
setSelection(VisibleSelection(base, extent, affinity), true, true, userTriggered);
}
-void SelectionController::setSelection(const VisibleSelection& s, bool closeTyping, bool clearTypingStyle, bool userTriggered, CursorAlignOnScroll align, TextGranularity granularity, DirectionalityPolicy directionalityPolicy)
+void SelectionController::setSelection(const VisibleSelection& s, bool closeTyping, bool shouldClearTypingStyle, bool userTriggered, CursorAlignOnScroll align, TextGranularity granularity, DirectionalityPolicy directionalityPolicy)
{
m_granularity = granularity;
@@ -134,15 +138,15 @@ void SelectionController::setSelection(const VisibleSelection& s, bool closeTypi
// <http://bugs.webkit.org/show_bug.cgi?id=23464>: Infinite recursion at SelectionController::setSelection
// if document->frame() == m_frame we can get into an infinite loop
if (document && document->frame() && document->frame() != m_frame && document != m_frame->document()) {
- document->frame()->selection()->setSelection(s, closeTyping, clearTypingStyle, userTriggered);
+ document->frame()->selection()->setSelection(s, closeTyping, shouldClearTypingStyle, userTriggered);
return;
}
if (closeTyping)
TypingCommand::closeTyping(m_frame->editor()->lastEditCommand());
- if (clearTypingStyle)
- m_frame->clearTypingStyle();
+ if (shouldClearTypingStyle)
+ clearTypingStyle();
if (m_selection == s)
return;
@@ -154,7 +158,7 @@ void SelectionController::setSelection(const VisibleSelection& s, bool closeTypi
m_caretRectNeedsUpdate = true;
if (!s.isNone())
- m_frame->setFocusedNodeIfNeeded();
+ setFocusedNodeIfNeeded();
updateAppearance();
@@ -162,7 +166,7 @@ void SelectionController::setSelection(const VisibleSelection& s, bool closeTypi
// It will be restored by the vertical arrow navigation code if necessary.
m_xPosForVerticalArrowNavigation = NoXPosForVerticalArrowNavigation;
selectFrameElementInParentIfFullySelected();
- m_frame->notifyRendererOfSelectionChange(userTriggered);
+ notifyRendererOfSelectionChange(userTriggered);
m_frame->editor()->respondToChangedSelection(oldSelection, closeTyping);
if (userTriggered) {
ScrollAlignment alignment;
@@ -172,7 +176,7 @@ void SelectionController::setSelection(const VisibleSelection& s, bool closeTypi
else
alignment = (align == AlignCursorOnScrollAlways) ? ScrollAlignment::alignTopAlways : ScrollAlignment::alignToEdgeIfNeeded;
- m_frame->revealSelection(alignment, true);
+ revealSelection(alignment, true);
}
notifyAccessibilityForSelectionChange();
@@ -287,8 +291,7 @@ void SelectionController::willBeModified(EAlteration alter, EDirection direction
TextDirection SelectionController::directionOfEnclosingBlock()
{
- Node* n = m_selection.extent().node();
- Node* enclosingBlockNode = enclosingBlock(n);
+ Node* enclosingBlockNode = enclosingBlock(m_selection.extent().node());
if (!enclosingBlockNode)
return LTR;
RenderObject* renderer = enclosingBlockNode->renderer();
@@ -638,7 +641,7 @@ bool SelectionController::modify(EAlteration alter, EDirection direction, TextGr
trialSelectionController.setIsDirectional(m_isDirectional);
trialSelectionController.modify(alter, direction, granularity, false, settings);
- bool change = m_frame->shouldChangeSelection(trialSelectionController.selection());
+ bool change = shouldChangeSelection(trialSelectionController.selection());
if (!change)
return false;
}
@@ -734,7 +737,7 @@ bool SelectionController::modify(EAlteration alter, int verticalDistance, bool u
trialSelectionController.setIsDirectional(m_isDirectional);
trialSelectionController.modify(alter, verticalDistance, false);
- bool change = m_frame->shouldChangeSelection(trialSelectionController.selection());
+ bool change = shouldChangeSelection(trialSelectionController.selection());
if (!change)
return false;
}
@@ -1243,7 +1246,7 @@ void SelectionController::selectFrameElementInParentIfFullySelected()
// Focus on the parent frame, and then select from before this element to after.
VisibleSelection newSelection(beforeOwnerElement, afterOwnerElement);
- if (parent->shouldChangeSelection(newSelection)) {
+ if (parent->selection()->shouldChangeSelection(newSelection)) {
page->focusController()->setFocusedFrame(parent);
parent->selection()->setSelection(newSelection);
}
@@ -1269,10 +1272,10 @@ void SelectionController::selectAll()
if (!root)
return;
VisibleSelection newSelection(VisibleSelection::selectionFromContentsOfNode(root));
- if (m_frame->shouldChangeSelection(newSelection))
+ if (shouldChangeSelection(newSelection))
setSelection(newSelection);
selectFrameElementInParentIfFullySelected();
- m_frame->notifyRendererOfSelectionChange(true);
+ notifyRendererOfSelectionChange(true);
}
bool SelectionController::setSelectedRange(Range* range, EAffinity affinity, bool closeTyping)
@@ -1329,7 +1332,7 @@ bool SelectionController::isInPasswordField() const
if (!startNode->hasTagName(inputTag))
return false;
- return static_cast<HTMLInputElement*>(startNode)->inputType() == HTMLInputElement::PASSWORD;
+ return static_cast<HTMLInputElement*>(startNode)->isPasswordField();
}
bool SelectionController::caretRendersInsideNode(Node* node) const
@@ -1347,11 +1350,11 @@ void SelectionController::focusedOrActiveStateChanged()
// RenderObject::selectionForegroundColor() check if the frame is active,
// we have to update places those colors were painted.
if (RenderView* view = toRenderView(m_frame->document()->renderer()))
- view->repaintRectangleInViewAndCompositedLayers(enclosingIntRect(m_frame->selectionBounds()));
+ view->repaintRectangleInViewAndCompositedLayers(enclosingIntRect(bounds()));
// Caret appears in the active frame.
if (activeAndFocused)
- m_frame->setSelectionFromNone();
+ setSelectionFromNone();
setCaretVisible(activeAndFocused);
// Update for caps lock state
@@ -1502,6 +1505,207 @@ void SelectionController::caretBlinkTimerFired(Timer<SelectionController>*)
#endif
}
+void SelectionController::notifyRendererOfSelectionChange(bool userTriggered)
+{
+ m_frame->document()->updateStyleIfNeeded();
+
+ if (!rootEditableElement())
+ return;
+
+ RenderObject* renderer = rootEditableElement()->shadowAncestorNode()->renderer();
+ if (!renderer || !renderer->isTextControl())
+ return;
+
+ toRenderTextControl(renderer)->selectionChanged(userTriggered);
+}
+
+// Helper function that tells whether a particular node is an element that has an entire
+// Frame and FrameView, a <frame>, <iframe>, or <object>.
+static bool isFrameElement(const Node* n)
+{
+ if (!n)
+ return false;
+ RenderObject* renderer = n->renderer();
+ if (!renderer || !renderer->isWidget())
+ return false;
+ Widget* widget = toRenderWidget(renderer)->widget();
+ return widget && widget->isFrameView();
+}
+
+void SelectionController::setFocusedNodeIfNeeded()
+{
+ if (isNone() || !isFocused())
+ return;
+
+ bool caretBrowsing = m_frame->settings() && m_frame->settings()->caretBrowsingEnabled();
+ if (caretBrowsing) {
+ if (Node* anchor = enclosingAnchorElement(base())) {
+ m_frame->page()->focusController()->setFocusedNode(anchor, m_frame);
+ return;
+ }
+ }
+
+ if (Node* target = rootEditableElement()) {
+ RenderObject* renderer = target->renderer();
+
+ // Walk up the render tree to search for a node to focus.
+ // Walking up the DOM tree wouldn't work for shadow trees, like those behind the engine-based text fields.
+ while (renderer) {
+ // We don't want to set focus on a subframe when selecting in a parent frame,
+ // so add the !isFrameElement check here. There's probably a better way to make this
+ // work in the long term, but this is the safest fix at this time.
+ if (target && target->isMouseFocusable() && !isFrameElement(target)) {
+ m_frame->page()->focusController()->setFocusedNode(target, m_frame);
+ return;
+ }
+ renderer = renderer->parent();
+ if (renderer)
+ target = renderer->node();
+ }
+ m_frame->document()->setFocusedNode(0);
+ }
+
+ if (caretBrowsing)
+ m_frame->page()->focusController()->setFocusedNode(0, m_frame);
+}
+
+void SelectionController::paintDragCaret(GraphicsContext* p, int tx, int ty, const IntRect& clipRect) const
+{
+#if ENABLE(TEXT_CARET)
+ SelectionController* dragCaretController = m_frame->page()->dragCaretController();
+ ASSERT(dragCaretController->selection().isCaret());
+ if (dragCaretController->selection().start().node()->document()->frame() == m_frame)
+ dragCaretController->paintCaret(p, tx, ty, clipRect);
+#else
+ UNUSED_PARAM(p);
+ UNUSED_PARAM(tx);
+ UNUSED_PARAM(ty);
+ UNUSED_PARAM(clipRect);
+#endif
+}
+
+bool SelectionController::shouldDeleteSelection(const VisibleSelection& selection) const
+{
+ return m_frame->editor()->client()->shouldDeleteRange(selection.toNormalizedRange().get());
+}
+
+FloatRect SelectionController::bounds(bool clipToVisibleContent) const
+{
+ RenderView* root = m_frame->contentRenderer();
+ FrameView* view = m_frame->view();
+ if (!root || !view)
+ return IntRect();
+
+ IntRect selectionRect = root->selectionBounds(clipToVisibleContent);
+ return clipToVisibleContent ? intersection(selectionRect, view->visibleContentRect()) : selectionRect;
+}
+
+void SelectionController::getClippedVisibleTextRectangles(Vector<FloatRect>& rectangles) const
+{
+ RenderView* root = m_frame->contentRenderer();
+ if (!root)
+ return;
+
+ FloatRect visibleContentRect = m_frame->view()->visibleContentRect();
+
+ Vector<FloatQuad> quads;
+ toNormalizedRange()->textQuads(quads, true);
+
+ // FIXME: We are appending empty rectangles to the list for those that fall outside visibleContentRect.
+ // It might be better to omit those rectangles entirely.
+ size_t size = quads.size();
+ for (size_t i = 0; i < size; ++i)
+ rectangles.append(intersection(quads[i].enclosingBoundingBox(), visibleContentRect));
+}
+
+// Scans logically forward from "start", including any child frames.
+static HTMLFormElement* scanForForm(Node* start)
+{
+ for (Node* node = start; node; node = node->traverseNextNode()) {
+ if (node->hasTagName(formTag))
+ return static_cast<HTMLFormElement*>(node);
+ if (node->isHTMLElement() && static_cast<HTMLElement*>(node)->isFormControlElement())
+ return static_cast<HTMLFormControlElement*>(node)->form();
+ if (node->hasTagName(frameTag) || node->hasTagName(iframeTag)) {
+ Node* childDocument = static_cast<HTMLFrameElementBase*>(node)->contentDocument();
+ if (HTMLFormElement* frameResult = scanForForm(childDocument))
+ return frameResult;
+ }
+ }
+ return 0;
+}
+
+// We look for either the form containing the current focus, or for one immediately after it
+HTMLFormElement* SelectionController::currentForm() const
+{
+ // Start looking either at the active (first responder) node, or where the selection is.
+ Node* start = m_frame->document()->focusedNode();
+ if (!start)
+ start = this->start().node();
+
+ // Try walking up the node tree to find a form element.
+ Node* node;
+ for (node = start; node; node = node->parentNode()) {
+ if (node->hasTagName(formTag))
+ return static_cast<HTMLFormElement*>(node);
+ if (node->isHTMLElement() && static_cast<HTMLElement*>(node)->isFormControlElement())
+ return static_cast<HTMLFormControlElement*>(node)->form();
+ }
+
+ // Try walking forward in the node tree to find a form element.
+ return scanForForm(start);
+}
+
+void SelectionController::revealSelection(const ScrollAlignment& alignment, bool revealExtent)
+{
+ IntRect rect;
+
+ switch (selectionType()) {
+ case VisibleSelection::NoSelection:
+ return;
+ case VisibleSelection::CaretSelection:
+ rect = absoluteCaretBounds();
+ break;
+ case VisibleSelection::RangeSelection:
+ rect = revealExtent ? VisiblePosition(extent()).absoluteCaretBounds() : enclosingIntRect(bounds(false));
+ break;
+ }
+
+ Position start = this->start();
+ ASSERT(start.node());
+ if (start.node() && start.node()->renderer()) {
+ // FIXME: This code only handles scrolling the startContainer's layer, but
+ // the selection rect could intersect more than just that.
+ // See <rdar://problem/4799899>.
+ if (RenderLayer* layer = start.node()->renderer()->enclosingLayer()) {
+ layer->scrollRectToVisible(rect, false, alignment, alignment);
+ updateAppearance();
+ }
+ }
+}
+
+void SelectionController::setSelectionFromNone()
+{
+ // Put a caret inside the body if the entire frame is editable (either the
+ // entire WebView is editable or designMode is on for this document).
+
+ Document* document = m_frame->document();
+ bool caretBrowsing = m_frame->settings() && m_frame->settings()->caretBrowsingEnabled();
+ if (!isNone() || !(m_frame->isContentEditable() || caretBrowsing))
+ return;
+
+ Node* node = document->documentElement();
+ while (node && !node->hasTagName(bodyTag))
+ node = node->traverseNextNode();
+ if (node)
+ setSelection(VisibleSelection(Position(node, 0), DOWNSTREAM));
+}
+
+bool SelectionController::shouldChangeSelection(const VisibleSelection& newSelection) const
+{
+ return m_frame->editor()->shouldChangeSelection(selection(), newSelection, newSelection.affinity(), false);
+}
+
#ifndef NDEBUG
void SelectionController::formatForDebugger(char* buffer, unsigned length) const
diff --git a/WebCore/editing/SelectionController.h b/WebCore/editing/SelectionController.h
index 5fa2769..90018cd 100644
--- a/WebCore/editing/SelectionController.h
+++ b/WebCore/editing/SelectionController.h
@@ -26,8 +26,10 @@
#ifndef SelectionController_h
#define SelectionController_h
+#include "CSSMutableStyleDeclaration.h"
#include "IntRect.h"
#include "Range.h"
+#include "ScrollBehavior.h"
#include "Timer.h"
#include "VisibleSelection.h"
#include <wtf/Noncopyable.h>
@@ -36,6 +38,7 @@ namespace WebCore {
class Frame;
class GraphicsContext;
+class HTMLFormElement;
class RenderObject;
class RenderView;
class Settings;
@@ -149,6 +152,26 @@ public:
void showTreeForThis() const;
#endif
+ bool shouldChangeSelection(const VisibleSelection&) const;
+ bool shouldDeleteSelection(const VisibleSelection&) const;
+ void setFocusedNodeIfNeeded();
+ void notifyRendererOfSelectionChange(bool userTriggered);
+
+ void paintDragCaret(GraphicsContext*, int tx, int ty, const IntRect& clipRect) const;
+
+ CSSMutableStyleDeclaration* typingStyle() const;
+ void setTypingStyle(PassRefPtr<CSSMutableStyleDeclaration>);
+ void clearTypingStyle();
+
+ FloatRect bounds(bool clipToVisibleContent = true) const;
+
+ void getClippedVisibleTextRectangles(Vector<FloatRect>&) const;
+
+ HTMLFormElement* currentForm() const;
+
+ void revealSelection(const ScrollAlignment& = ScrollAlignment::alignCenterIfNeeded, bool revealExtent = false);
+ void setSelectionFromNone();
+
private:
enum EPositionType { START, END, BASE, EXTENT };
@@ -193,6 +216,8 @@ private:
VisibleSelection m_selection;
TextGranularity m_granularity;
+ RefPtr<CSSMutableStyleDeclaration> m_typingStyle;
+
Timer<SelectionController> m_caretBlinkTimer;
IntRect m_caretRect; // caret rect in coords local to the renderer responsible for painting the caret
@@ -209,6 +234,21 @@ private:
bool m_caretPaint;
};
+inline CSSMutableStyleDeclaration* SelectionController::typingStyle() const
+{
+ return m_typingStyle.get();
+}
+
+inline void SelectionController::clearTypingStyle()
+{
+ m_typingStyle.clear();
+}
+
+inline void SelectionController::setTypingStyle(PassRefPtr<CSSMutableStyleDeclaration> style)
+{
+ m_typingStyle = style;
+}
+
#if !(PLATFORM(MAC) || PLATFORM(GTK))
inline void SelectionController::notifyAccessibilityForSelectionChange()
{
diff --git a/WebCore/editing/TextIterator.cpp b/WebCore/editing/TextIterator.cpp
index daba80e..d5a03ef 100644
--- a/WebCore/editing/TextIterator.cpp
+++ b/WebCore/editing/TextIterator.cpp
@@ -261,6 +261,7 @@ TextIterator::TextIterator()
, m_entersTextControls(false)
, m_emitsTextWithoutTranscoding(false)
, m_handledFirstLetter(false)
+ , m_ignoresStyleVisibility(false)
{
}
@@ -278,6 +279,7 @@ TextIterator::TextIterator(const Range* r, TextIteratorBehavior behavior)
, m_entersTextControls(behavior & TextIteratorEntersTextControls)
, m_emitsTextWithoutTranscoding(behavior & TextIteratorEmitsTextsWithoutTranscoding)
, m_handledFirstLetter(false)
+ , m_ignoresStyleVisibility(behavior & TextIteratorIgnoresStyleVisibility)
{
// FIXME: should support TextIteratorEndsAtEditingBoundary http://webkit.org/b/43609
ASSERT(behavior != TextIteratorEndsAtEditingBoundary);
@@ -444,7 +446,7 @@ void TextIterator::advance()
bool TextIterator::handleTextNode()
{
- if (m_fullyClippedStack.top())
+ if (m_fullyClippedStack.top() && !m_ignoresStyleVisibility)
return false;
RenderText* renderer = toRenderText(m_node->renderer());
@@ -469,7 +471,7 @@ bool TextIterator::handleTextNode()
return false;
}
}
- if (renderer->style()->visibility() != VISIBLE)
+ if (renderer->style()->visibility() != VISIBLE && !m_ignoresStyleVisibility)
return false;
int strLength = str.length();
int end = (m_node == m_endContainer) ? m_endOffset : INT_MAX;
@@ -490,7 +492,7 @@ bool TextIterator::handleTextNode()
return false;
}
}
- if (renderer->style()->visibility() != VISIBLE)
+ if (renderer->style()->visibility() != VISIBLE && !m_ignoresStyleVisibility)
return false;
m_lastTextNodeEndedWithCollapsedSpace = true; // entire block is collapsed space
return true;
@@ -516,7 +518,7 @@ bool TextIterator::handleTextNode()
void TextIterator::handleTextBox()
{
RenderText* renderer = m_firstLetterText ? m_firstLetterText : toRenderText(m_node->renderer());
- if (renderer->style()->visibility() != VISIBLE) {
+ if (renderer->style()->visibility() != VISIBLE && !m_ignoresStyleVisibility) {
m_textBox = 0;
return;
}
@@ -600,7 +602,7 @@ void TextIterator::handleTextNodeFirstLetter(RenderTextFragment* renderer)
{
if (renderer->firstLetter()) {
RenderObject* r = renderer->firstLetter();
- if (r->style()->visibility() != VISIBLE)
+ if (r->style()->visibility() != VISIBLE && !m_ignoresStyleVisibility)
return;
for (RenderObject *currChild = r->firstChild(); currChild; currChild->nextSibling()) {
if (currChild->isText()) {
@@ -622,7 +624,7 @@ bool TextIterator::handleReplacedElement()
return false;
RenderObject* renderer = m_node->renderer();
- if (renderer->style()->visibility() != VISIBLE)
+ if (renderer->style()->visibility() != VISIBLE && !m_ignoresStyleVisibility)
return false;
if (m_lastTextNodeEndedWithCollapsedSpace) {
@@ -2214,7 +2216,7 @@ PassRefPtr<Range> TextIterator::rangeFromLocationAndLength(Element* scope, int r
// --------
-UChar* plainTextToMallocAllocatedBuffer(const Range* r, unsigned& bufferLength, bool isDisplayString)
+UChar* plainTextToMallocAllocatedBuffer(const Range* r, unsigned& bufferLength, bool isDisplayString, TextIteratorBehavior defaultBehavior)
{
UChar* result = 0;
@@ -2226,7 +2228,11 @@ UChar* plainTextToMallocAllocatedBuffer(const Range* r, unsigned& bufferLength,
OwnPtr<Vector<TextSegment> > textSegments;
Vector<UChar> textBuffer;
textBuffer.reserveInitialCapacity(cMaxSegmentSize);
- for (TextIterator it(r, isDisplayString ? TextIteratorDefaultBehavior : TextIteratorEmitsTextsWithoutTranscoding); !it.atEnd(); it.advance()) {
+ TextIteratorBehavior behavior = defaultBehavior;
+ if (!isDisplayString)
+ behavior = static_cast<TextIteratorBehavior>(behavior | TextIteratorEmitsTextsWithoutTranscoding);
+
+ for (TextIterator it(r, behavior); !it.atEnd(); it.advance()) {
if (textBuffer.size() && textBuffer.size() + it.length() > cMaxSegmentSize) {
UChar* newSegmentBuffer = static_cast<UChar*>(malloc(textBuffer.size() * sizeof(UChar)));
if (!newSegmentBuffer)
@@ -2275,10 +2281,10 @@ exit:
return result;
}
-String plainText(const Range* r)
+String plainText(const Range* r, TextIteratorBehavior defaultBehavior)
{
unsigned length;
- UChar* buf = plainTextToMallocAllocatedBuffer(r, length, false);
+ UChar* buf = plainTextToMallocAllocatedBuffer(r, length, false, defaultBehavior);
if (!buf)
return "";
String result(buf, length);
diff --git a/WebCore/editing/TextIterator.h b/WebCore/editing/TextIterator.h
index 805e060..7fd87bd 100644
--- a/WebCore/editing/TextIterator.h
+++ b/WebCore/editing/TextIterator.h
@@ -35,6 +35,15 @@ namespace WebCore {
class RenderText;
class RenderTextFragment;
+enum TextIteratorBehavior {
+ TextIteratorDefaultBehavior = 0,
+ TextIteratorEmitsCharactersBetweenAllVisiblePositions = 1 << 0,
+ TextIteratorEntersTextControls = 1 << 1,
+ TextIteratorEmitsTextsWithoutTranscoding = 1 << 2,
+ TextIteratorEndsAtEditingBoundary = 1 << 3,
+ TextIteratorIgnoresStyleVisibility = 1 << 4
+};
+
// FIXME: Can't really answer this question correctly without knowing the white-space mode.
// FIXME: Move this somewhere else in the editing directory. It doesn't belong here.
inline bool isCollapsibleWhitespace(UChar c)
@@ -48,8 +57,8 @@ inline bool isCollapsibleWhitespace(UChar c)
}
}
-String plainText(const Range*);
-UChar* plainTextToMallocAllocatedBuffer(const Range*, unsigned& bufferLength, bool isDisplayString);
+String plainText(const Range*, TextIteratorBehavior defaultBehavior = TextIteratorDefaultBehavior);
+UChar* plainTextToMallocAllocatedBuffer(const Range*, unsigned& bufferLength, bool isDisplayString, TextIteratorBehavior defaultBehavior = TextIteratorDefaultBehavior);
PassRefPtr<Range> findPlainText(const Range*, const String&, bool forward, bool caseSensitive);
class BitStack {
@@ -71,14 +80,6 @@ private:
// at points where replaced elements break up the text flow. The text comes back in
// chunks so as to optimize for performance of the iteration.
-enum TextIteratorBehavior {
- TextIteratorDefaultBehavior = 0,
- TextIteratorEmitsCharactersBetweenAllVisiblePositions = 1 << 0,
- TextIteratorEntersTextControls = 1 << 1,
- TextIteratorEmitsTextsWithoutTranscoding = 1 << 2,
- TextIteratorEndsAtEditingBoundary = 1 << 3
-};
-
class TextIterator {
public:
TextIterator();
@@ -173,6 +174,8 @@ private:
bool m_emitsTextWithoutTranscoding;
// Used when deciding text fragment created by :first-letter should be looked into.
bool m_handledFirstLetter;
+ // Used when the visibility of the style should not affect text gathering.
+ bool m_ignoresStyleVisibility;
};
// Iterates through the DOM range, returning all the text, and 0-length boundaries
diff --git a/WebCore/editing/TypingCommand.cpp b/WebCore/editing/TypingCommand.cpp
index 81a6d5c..1d1183a 100644
--- a/WebCore/editing/TypingCommand.cpp
+++ b/WebCore/editing/TypingCommand.cpp
@@ -356,7 +356,7 @@ void TypingCommand::insertText(const String &text, bool selectInsertedText)
void TypingCommand::insertTextRunWithoutNewlines(const String &text, bool selectInsertedText)
{
RefPtr<InsertTextCommand> command;
- if (!document()->frame()->typingStyle() && !m_commands.isEmpty()) {
+ if (!document()->frame()->selection()->typingStyle() && !m_commands.isEmpty()) {
EditCommand* lastCommand = m_commands.last().get();
if (lastCommand->isInsertTextCommand())
command = static_cast<InsertTextCommand*>(lastCommand);
@@ -496,7 +496,7 @@ void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing)
if (selectionToDelete.isNone())
return;
- if (selectionToDelete.isCaret() || !document()->frame()->shouldDeleteSelection(selectionToDelete))
+ if (selectionToDelete.isCaret() || !document()->frame()->selection()->shouldDeleteSelection(selectionToDelete))
return;
if (killRing)
@@ -579,7 +579,7 @@ void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool ki
if (selectionToDelete.isNone())
return;
- if (selectionToDelete.isCaret() || !document()->frame()->shouldDeleteSelection(selectionToDelete))
+ if (selectionToDelete.isCaret() || !document()->frame()->selection()->shouldDeleteSelection(selectionToDelete))
return;
if (killRing)
diff --git a/WebCore/editing/mac/EditorMac.mm b/WebCore/editing/mac/EditorMac.mm
index ac658c8..f010908 100644
--- a/WebCore/editing/mac/EditorMac.mm
+++ b/WebCore/editing/mac/EditorMac.mm
@@ -29,9 +29,14 @@
#import "ColorMac.h"
#import "ClipboardMac.h"
#import "CachedResourceLoader.h"
+#import "DocumentFragment.h"
+#import "Editor.h"
+#import "EditorClient.h"
#import "Frame.h"
#import "FrameView.h"
+#import "Pasteboard.h"
#import "RenderBlock.h"
+#import "RuntimeApplicationChecks.h"
namespace WebCore {
@@ -55,20 +60,32 @@ void Editor::showColorPanel()
[[NSApplication sharedApplication] orderFrontColorPanel:nil];
}
-// FIXME: We want to use the platform-independent code instead. But when we last
-// tried to do so it seemed that we first need to move more of the logic from
-// -[WebHTMLView.cpp _documentFragmentFromPasteboard] into PasteboardMac.
-
-void Editor::paste()
+void Editor::pasteWithPasteboard(Pasteboard* pasteboard, bool allowPlainText)
{
- ASSERT(m_frame->document());
- FrameView* view = m_frame->view();
- if (!view)
- return;
- CachedResourceLoader* loader = m_frame->document()->cachedResourceLoader();
- loader->setAllowStaleResources(true);
- [view->documentView() tryToPerform:@selector(paste:) with:nil];
- loader->setAllowStaleResources(false);
+ RefPtr<Range> range = selectedRange();
+ bool choosePlainText;
+
+ m_frame->editor()->client()->setInsertionPasteboard([NSPasteboard generalPasteboard]);
+#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
+ RefPtr<DocumentFragment> fragment = pasteboard->documentFragment(m_frame, range, allowPlainText, choosePlainText);
+ if (fragment && shouldInsertFragment(fragment, range, EditorInsertActionPasted))
+ pasteAsFragment(fragment, canSmartReplaceWithPasteboard(pasteboard), false);
+#else
+ // Mail is ignoring the frament passed to the delegate and creates a new one.
+ // We want to avoid creating the fragment twice.
+ if (applicationIsAppleMail()) {
+ if (shouldInsertFragment(NULL, range, EditorInsertActionPasted)) {
+ RefPtr<DocumentFragment> fragment = pasteboard->documentFragment(m_frame, range, allowPlainText, choosePlainText);
+ if (fragment)
+ pasteAsFragment(fragment, canSmartReplaceWithPasteboard(pasteboard), false);
+ }
+ } else {
+ RefPtr<DocumentFragment>fragment = pasteboard->documentFragment(m_frame, range, allowPlainText, choosePlainText);
+ if (fragment && shouldInsertFragment(fragment, range, EditorInsertActionPasted))
+ pasteAsFragment(fragment, canSmartReplaceWithPasteboard(pasteboard), false);
+ }
+#endif
+ m_frame->editor()->client()->setInsertionPasteboard(nil);
}
NSDictionary* Editor::fontAttributesForSelectionStart() const
diff --git a/WebCore/editing/markup.cpp b/WebCore/editing/markup.cpp
index d4996ea..a279808 100644
--- a/WebCore/editing/markup.cpp
+++ b/WebCore/editing/markup.cpp
@@ -190,34 +190,6 @@ private:
const bool m_shouldResolveURLs;
};
-class StyledMarkupAccumulator : public MarkupAccumulator {
-public:
- enum RangeFullySelectsNode { DoesFullySelectNode, DoesNotFullySelectNode };
-
- StyledMarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs shouldResolveURLs, EAnnotateForInterchange shouldAnnotate, const Range* range)
- : MarkupAccumulator(nodes, shouldResolveURLs, range)
- , m_shouldAnnotate(shouldAnnotate)
- {
- }
- void wrapWithNode(Node*, bool convertBlocksToInlines = false, RangeFullySelectsNode = DoesFullySelectNode);
- void wrapWithStyleNode(CSSStyleDeclaration*, Document*, bool isBlock = false);
- String takeResults();
-
-protected:
- virtual void appendText(Vector<UChar>& out, Text*);
- String renderedText(const Node*, const Range*);
- String stringValueForRange(const Node*, const Range*);
- void removeExteriorStyles(CSSMutableStyleDeclaration*);
- void appendElement(Vector<UChar>& out, Element* element, bool addDisplayInline, RangeFullySelectsNode);
- void appendElement(Vector<UChar>& out, Element* element, Namespaces*) { appendElement(out, element, false, DoesFullySelectNode); }
-
- bool shouldAnnotate() { return m_shouldAnnotate == AnnotateForInterchange; }
-
-private:
- Vector<String> m_reversedPrecedingMarkup;
- const EAnnotateForInterchange m_shouldAnnotate;
-};
-
void MarkupAccumulator::appendString(const String& string)
{
m_succeedingMarkup.append(string);
@@ -239,37 +211,6 @@ void MarkupAccumulator::appendEndTag(Node* node)
m_succeedingMarkup.append(String::adopt(markup));
}
-void StyledMarkupAccumulator::wrapWithNode(Node* node, bool convertBlocksToInlines, RangeFullySelectsNode rangeFullySelectsNode)
-{
- Vector<UChar> markup;
- if (node->isElementNode())
- appendElement(markup, static_cast<Element*>(node), convertBlocksToInlines && isBlock(const_cast<Node*>(node)), rangeFullySelectsNode);
- else
- appendStartMarkup(markup, node, 0);
- m_reversedPrecedingMarkup.append(String::adopt(markup));
- appendEndTag(node);
- if (m_nodes)
- m_nodes->append(node);
-}
-
-void StyledMarkupAccumulator::wrapWithStyleNode(CSSStyleDeclaration* style, Document* document, bool isBlock)
-{
- // All text-decoration-related elements should have been treated as special ancestors
- // If we ever hit this ASSERT, we should export StyleChange in ApplyStyleCommand and use it here
- ASSERT(propertyMissingOrEqualToNone(style, CSSPropertyTextDecoration) && propertyMissingOrEqualToNone(style, CSSPropertyWebkitTextDecorationsInEffect));
- DEFINE_STATIC_LOCAL(const String, divStyle, ("<div style=\""));
- DEFINE_STATIC_LOCAL(const String, divClose, ("</div>"));
- DEFINE_STATIC_LOCAL(const String, styleSpanOpen, ("<span class=\"" AppleStyleSpanClass "\" style=\""));
- DEFINE_STATIC_LOCAL(const String, styleSpanClose, ("</span>"));
- Vector<UChar> openTag;
- append(openTag, isBlock ? divStyle : styleSpanOpen);
- appendAttributeValue(openTag, style->cssText(), document->isHTMLDocument());
- openTag.append('\"');
- openTag.append('>');
- m_reversedPrecedingMarkup.append(String::adopt(openTag));
- m_succeedingMarkup.append(isBlock ? divClose : styleSpanClose);
-}
-
// 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.
@@ -290,30 +231,6 @@ String MarkupAccumulator::takeResults()
return String::adopt(result);
}
-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);
-
- for (size_t i = preCount; i > 0; --i)
- append(result, m_reversedPrecedingMarkup[i - 1]);
-
- 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(),
@@ -344,20 +261,6 @@ void MarkupAccumulator::appendQuotedURLAttributeValue(Vector<UChar>& result, con
result.append(quoteChar);
}
-String StyledMarkupAccumulator::stringValueForRange(const Node* node, const Range* range)
-{
- if (!range)
- return node->nodeValue();
-
- String str = node->nodeValue();
- ExceptionCode ec;
- if (node == range->endContainer(ec))
- str.truncate(range->endOffset(ec));
- if (node == range->startContainer(ec))
- str.remove(0, range->startOffset(ec));
- return str;
-}
-
void MarkupAccumulator::appendNodeValue(Vector<UChar>& out, const Node* node, const Range* range, EntityMask entityMask)
{
String str = node->nodeValue();
@@ -378,59 +281,6 @@ void MarkupAccumulator::appendNodeValue(Vector<UChar>& out, const Node* node, co
appendCharactersReplacingEntities(out, characters, length, entityMask);
}
-String StyledMarkupAccumulator::renderedText(const Node* node, const Range* range)
-{
- if (!node->isTextNode())
- return String();
-
- ExceptionCode ec;
- const Text* textNode = static_cast<const Text*>(node);
- unsigned startOffset = 0;
- unsigned endOffset = textNode->length();
-
- if (range && node == range->startContainer(ec))
- startOffset = range->startOffset(ec);
- if (range && node == range->endContainer(ec))
- endOffset = range->endOffset(ec);
-
- Position start(const_cast<Node*>(node), startOffset);
- Position end(const_cast<Node*>(node), endOffset);
- return plainText(Range::create(node->document(), start, end).get());
-}
-
-static PassRefPtr<CSSMutableStyleDeclaration> styleFromMatchedRulesForElement(Element* element, bool authorOnly = true)
-{
- RefPtr<CSSMutableStyleDeclaration> style = CSSMutableStyleDeclaration::create();
- RefPtr<CSSRuleList> matchedRules = element->document()->styleSelector()->styleRulesForElement(element, authorOnly);
- if (matchedRules) {
- for (unsigned i = 0; i < matchedRules->length(); i++) {
- if (matchedRules->item(i)->type() == CSSRule::STYLE_RULE) {
- RefPtr<CSSMutableStyleDeclaration> s = static_cast<CSSStyleRule*>(matchedRules->item(i))->style();
- style->merge(s.get(), true);
- }
- }
- }
-
- return style.release();
-}
-
-static void removeEnclosingMailBlockquoteStyle(CSSMutableStyleDeclaration* style, Node* node)
-{
- Node* blockquote = nearestMailBlockquote(node);
- if (!blockquote || !blockquote->parentNode())
- return;
-
- removeStylesAddedByNode(style, blockquote);
-}
-
-static void removeDefaultStyles(CSSMutableStyleDeclaration* style, Document* document)
-{
- if (!document || !document->documentElement())
- return;
-
- prepareEditingStyleToApplyAt(style, Position(document->documentElement(), 0));
-}
-
bool MarkupAccumulator::shouldAddNamespaceElement(const Element* element)
{
// Don't add namespace attribute if it is already defined for this elem.
@@ -500,20 +350,6 @@ void MarkupAccumulator::appendText(Vector<UChar>& out, Text* text)
appendNodeValue(out, text, m_range, entityMaskForText(text));
}
-void StyledMarkupAccumulator::appendText(Vector<UChar>& out, Text* text)
-{
- if (!shouldAnnotate() || (text->parentElement() && text->parentElement()->tagQName() == textareaTag)) {
- MarkupAccumulator::appendText(out, text);
- return;
- }
-
- bool useRenderedText = !enclosingNodeWithTag(Position(text, 0), selectTag);
- String content = useRenderedText ? renderedText(text, m_range) : stringValueForRange(text, m_range);
- Vector<UChar> buffer;
- appendCharactersReplacingEntities(buffer, content.characters(), content.length(), EntityMaskInPCDATA);
- append(out, convertHTMLTextToInterchangeFormat(String::adopt(buffer), 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 "-->".
@@ -561,11 +397,6 @@ void MarkupAccumulator::appendProcessingInstruction(Vector<UChar>& out, const St
append(out, "?>");
}
-void StyledMarkupAccumulator::removeExteriorStyles(CSSMutableStyleDeclaration* style)
-{
- style->removeProperty(CSSPropertyFloat);
-}
-
void MarkupAccumulator::appendElement(Vector<UChar>& out, Element* element, Namespaces* namespaces)
{
appendOpenTag(out, element, namespaces);
@@ -578,66 +409,6 @@ void MarkupAccumulator::appendElement(Vector<UChar>& out, Element* element, Name
appendCloseTag(out, element);
}
-void StyledMarkupAccumulator::appendElement(Vector<UChar>& out, Element* element, bool addDisplayInline, RangeFullySelectsNode rangeFullySelectsNode)
-{
- bool documentIsHTML = element->document()->isHTMLDocument();
- appendOpenTag(out, element, 0);
-
- NamedNodeMap* attributes = element->attributes();
- unsigned length = attributes->length();
- for (unsigned int i = 0; i < length; i++) {
- Attribute* attribute = attributes->attributeItem(i);
- // We'll handle the style attribute separately, below.
- if (attribute->name() == styleAttr && element->isHTMLElement() && (shouldAnnotate() || addDisplayInline))
- continue;
- appendAttribute(out, element, *attribute, 0);
- }
-
- if (element->isHTMLElement() && (shouldAnnotate() || addDisplayInline)) {
- RefPtr<CSSMutableStyleDeclaration> style = static_cast<HTMLElement*>(element)->getInlineStyleDecl()->copy();
- if (shouldAnnotate()) {
- RefPtr<CSSMutableStyleDeclaration> styleFromMatchedRules = styleFromMatchedRulesForElement(const_cast<Element*>(element));
- // Styles from the inline style declaration, held in the variable "style", take precedence
- // over those from matched rules.
- styleFromMatchedRules->merge(style.get());
- style = styleFromMatchedRules;
-
- RefPtr<CSSComputedStyleDeclaration> computedStyleForElement = computedStyle(element);
- RefPtr<CSSMutableStyleDeclaration> fromComputedStyle = CSSMutableStyleDeclaration::create();
-
- {
- CSSMutableStyleDeclaration::const_iterator end = style->end();
- for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) {
- const CSSProperty& property = *it;
- CSSValue* value = property.value();
- // The property value, if it's a percentage, may not reflect the actual computed value.
- // For example: style="height: 1%; overflow: visible;" in quirksmode
- // FIXME: There are others like this, see <rdar://problem/5195123> Slashdot copy/paste fidelity problem
- if (value->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE)
- if (static_cast<CSSPrimitiveValue*>(value)->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE)
- if (RefPtr<CSSValue> computedPropertyValue = computedStyleForElement->getPropertyCSSValue(property.id()))
- fromComputedStyle->addParsedProperty(CSSProperty(property.id(), computedPropertyValue));
- }
- }
- style->merge(fromComputedStyle.get());
- }
- if (addDisplayInline)
- style->setProperty(CSSPropertyDisplay, CSSValueInline, true);
- // If the node is not fully selected by the range, then we don't want to keep styles that affect its relationship to the nodes around it
- // only the ones that affect it and the nodes within it.
- if (rangeFullySelectsNode == DoesNotFullySelectNode)
- removeExteriorStyles(style.get());
- if (style->length() > 0) {
- DEFINE_STATIC_LOCAL(const String, stylePrefix, (" style=\""));
- append(out, stylePrefix);
- appendAttributeValue(out, style->cssText(), documentIsHTML);
- out.append('\"');
- }
- }
-
- appendCloseTag(out, element);
-}
-
void MarkupAccumulator::appendOpenTag(Vector<UChar>& out, Element* element, Namespaces* namespaces)
{
out.append('<');
@@ -794,30 +565,219 @@ static void completeURLs(Node* node, const String& baseURL)
for (size_t i = 0; i < numChanges; ++i)
changes[i].apply();
}
+
+class StyledMarkupAccumulator : public MarkupAccumulator {
+public:
+ enum RangeFullySelectsNode { DoesFullySelectNode, DoesNotFullySelectNode };
-static bool needInterchangeNewlineAfter(const VisiblePosition& v)
+ StyledMarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs shouldResolveURLs, EAnnotateForInterchange shouldAnnotate, const Range* range)
+ : MarkupAccumulator(nodes, shouldResolveURLs, range)
+ , m_shouldAnnotate(shouldAnnotate)
+ {
+ }
+ void wrapWithNode(Node*, bool convertBlocksToInlines = false, RangeFullySelectsNode = DoesFullySelectNode);
+ void wrapWithStyleNode(CSSStyleDeclaration*, Document*, bool isBlock = false);
+ String takeResults();
+
+protected:
+ virtual void appendText(Vector<UChar>& out, Text*);
+ String renderedText(const Node*, const Range*);
+ String stringValueForRange(const Node*, const Range*);
+ void removeExteriorStyles(CSSMutableStyleDeclaration*);
+ void appendElement(Vector<UChar>& out, Element* element, bool addDisplayInline, RangeFullySelectsNode);
+ void appendElement(Vector<UChar>& out, Element* element, Namespaces*) { appendElement(out, element, false, DoesFullySelectNode); }
+
+ bool shouldAnnotate() { return m_shouldAnnotate == AnnotateForInterchange; }
+
+private:
+ Vector<String> m_reversedPrecedingMarkup;
+ const EAnnotateForInterchange m_shouldAnnotate;
+};
+
+void StyledMarkupAccumulator::wrapWithNode(Node* node, bool convertBlocksToInlines, RangeFullySelectsNode rangeFullySelectsNode)
{
- VisiblePosition next = v.next();
- Node* upstreamNode = next.deepEquivalent().upstream().node();
- Node* downstreamNode = v.deepEquivalent().downstream().node();
- // Add an interchange newline if a paragraph break is selected and a br won't already be added to the markup to represent it.
- return isEndOfParagraph(v) && isStartOfParagraph(next) && !(upstreamNode->hasTagName(brTag) && upstreamNode == downstreamNode);
+ Vector<UChar> markup;
+ if (node->isElementNode())
+ appendElement(markup, static_cast<Element*>(node), convertBlocksToInlines && isBlock(const_cast<Node*>(node)), rangeFullySelectsNode);
+ else
+ appendStartMarkup(markup, node, 0);
+ m_reversedPrecedingMarkup.append(String::adopt(markup));
+ appendEndTag(node);
+ if (m_nodes)
+ m_nodes->append(node);
}
-static PassRefPtr<CSSMutableStyleDeclaration> styleFromMatchedRulesAndInlineDecl(const Node* node)
+void StyledMarkupAccumulator::wrapWithStyleNode(CSSStyleDeclaration* style, Document* document, bool isBlock)
{
- if (!node->isHTMLElement())
- return 0;
+ // All text-decoration-related elements should have been treated as special ancestors
+ // If we ever hit this ASSERT, we should export StyleChange in ApplyStyleCommand and use it here
+ ASSERT(propertyMissingOrEqualToNone(style, CSSPropertyTextDecoration) && propertyMissingOrEqualToNone(style, CSSPropertyWebkitTextDecorationsInEffect));
+ DEFINE_STATIC_LOCAL(const String, divStyle, ("<div style=\""));
+ DEFINE_STATIC_LOCAL(const String, divClose, ("</div>"));
+ DEFINE_STATIC_LOCAL(const String, styleSpanOpen, ("<span class=\"" AppleStyleSpanClass "\" style=\""));
+ DEFINE_STATIC_LOCAL(const String, styleSpanClose, ("</span>"));
+ Vector<UChar> openTag;
+ append(openTag, isBlock ? divStyle : styleSpanOpen);
+ appendAttributeValue(openTag, style->cssText(), document->isHTMLDocument());
+ openTag.append('\"');
+ openTag.append('>');
+ m_reversedPrecedingMarkup.append(String::adopt(openTag));
+ m_succeedingMarkup.append(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);
+
+ for (size_t i = preCount; i > 0; --i)
+ append(result, m_reversedPrecedingMarkup[i - 1]);
+
+ for (size_t i = 0; i < postCount; ++i)
+ append(result, m_succeedingMarkup[i]);
+
+ return String::adopt(result);
+}
+
+void StyledMarkupAccumulator::appendText(Vector<UChar>& out, Text* text)
+{
+ if (!shouldAnnotate() || (text->parentElement() && text->parentElement()->tagQName() == textareaTag)) {
+ MarkupAccumulator::appendText(out, text);
+ return;
+ }
+
+ bool useRenderedText = !enclosingNodeWithTag(Position(text, 0), selectTag);
+ String content = useRenderedText ? renderedText(text, m_range) : stringValueForRange(text, m_range);
+ Vector<UChar> buffer;
+ appendCharactersReplacingEntities(buffer, content.characters(), content.length(), EntityMaskInPCDATA);
+ append(out, convertHTMLTextToInterchangeFormat(String::adopt(buffer), text));
+}
- // FIXME: Having to const_cast here is ugly, but it is quite a bit of work to untangle
- // the non-const-ness of styleFromMatchedRulesForElement.
- HTMLElement* element = const_cast<HTMLElement*>(static_cast<const HTMLElement*>(node));
- RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesForElement(element);
- RefPtr<CSSMutableStyleDeclaration> inlineStyleDecl = element->getInlineStyleDecl();
- style->merge(inlineStyleDecl.get());
+String StyledMarkupAccumulator::renderedText(const Node* node, const Range* range)
+{
+ if (!node->isTextNode())
+ return String();
+
+ ExceptionCode ec;
+ const Text* textNode = static_cast<const Text*>(node);
+ unsigned startOffset = 0;
+ unsigned endOffset = textNode->length();
+
+ if (range && node == range->startContainer(ec))
+ startOffset = range->startOffset(ec);
+ if (range && node == range->endContainer(ec))
+ endOffset = range->endOffset(ec);
+
+ Position start(const_cast<Node*>(node), startOffset);
+ Position end(const_cast<Node*>(node), endOffset);
+ return plainText(Range::create(node->document(), start, end).get());
+}
+
+String StyledMarkupAccumulator::stringValueForRange(const Node* node, const Range* range)
+{
+ if (!range)
+ return node->nodeValue();
+
+ String str = node->nodeValue();
+ ExceptionCode ec;
+ if (node == range->endContainer(ec))
+ str.truncate(range->endOffset(ec));
+ if (node == range->startContainer(ec))
+ str.remove(0, range->startOffset(ec));
+ return str;
+}
+
+static PassRefPtr<CSSMutableStyleDeclaration> styleFromMatchedRulesForElement(Element* element, bool authorOnly = true)
+{
+ RefPtr<CSSMutableStyleDeclaration> style = CSSMutableStyleDeclaration::create();
+ RefPtr<CSSRuleList> matchedRules = element->document()->styleSelector()->styleRulesForElement(element, authorOnly);
+ if (matchedRules) {
+ for (unsigned i = 0; i < matchedRules->length(); i++) {
+ if (matchedRules->item(i)->type() == CSSRule::STYLE_RULE) {
+ RefPtr<CSSMutableStyleDeclaration> s = static_cast<CSSStyleRule*>(matchedRules->item(i))->style();
+ style->merge(s.get(), true);
+ }
+ }
+ }
+
return style.release();
}
+void StyledMarkupAccumulator::appendElement(Vector<UChar>& out, Element* element, bool addDisplayInline, RangeFullySelectsNode rangeFullySelectsNode)
+{
+ bool documentIsHTML = element->document()->isHTMLDocument();
+ appendOpenTag(out, element, 0);
+
+ NamedNodeMap* attributes = element->attributes();
+ unsigned length = attributes->length();
+ for (unsigned int i = 0; i < length; i++) {
+ Attribute* attribute = attributes->attributeItem(i);
+ // We'll handle the style attribute separately, below.
+ if (attribute->name() == styleAttr && element->isHTMLElement() && (shouldAnnotate() || addDisplayInline))
+ continue;
+ appendAttribute(out, element, *attribute, 0);
+ }
+
+ if (element->isHTMLElement() && (shouldAnnotate() || addDisplayInline)) {
+ RefPtr<CSSMutableStyleDeclaration> style = static_cast<HTMLElement*>(element)->getInlineStyleDecl()->copy();
+ if (shouldAnnotate()) {
+ RefPtr<CSSMutableStyleDeclaration> styleFromMatchedRules = styleFromMatchedRulesForElement(const_cast<Element*>(element));
+ // Styles from the inline style declaration, held in the variable "style", take precedence
+ // over those from matched rules.
+ styleFromMatchedRules->merge(style.get());
+ style = styleFromMatchedRules;
+
+ RefPtr<CSSComputedStyleDeclaration> computedStyleForElement = computedStyle(element);
+ RefPtr<CSSMutableStyleDeclaration> fromComputedStyle = CSSMutableStyleDeclaration::create();
+
+ {
+ CSSMutableStyleDeclaration::const_iterator end = style->end();
+ for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) {
+ const CSSProperty& property = *it;
+ CSSValue* value = property.value();
+ // The property value, if it's a percentage, may not reflect the actual computed value.
+ // For example: style="height: 1%; overflow: visible;" in quirksmode
+ // FIXME: There are others like this, see <rdar://problem/5195123> Slashdot copy/paste fidelity problem
+ if (value->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE)
+ if (static_cast<CSSPrimitiveValue*>(value)->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE)
+ if (RefPtr<CSSValue> computedPropertyValue = computedStyleForElement->getPropertyCSSValue(property.id()))
+ fromComputedStyle->addParsedProperty(CSSProperty(property.id(), computedPropertyValue));
+ }
+ }
+ style->merge(fromComputedStyle.get());
+ }
+ if (addDisplayInline)
+ style->setProperty(CSSPropertyDisplay, CSSValueInline, true);
+ // If the node is not fully selected by the range, then we don't want to keep styles that affect its relationship to the nodes around it
+ // only the ones that affect it and the nodes within it.
+ if (rangeFullySelectsNode == DoesNotFullySelectNode)
+ removeExteriorStyles(style.get());
+ if (style->length() > 0) {
+ DEFINE_STATIC_LOCAL(const String, stylePrefix, (" style=\""));
+ append(out, stylePrefix);
+ appendAttributeValue(out, style->cssText(), documentIsHTML);
+ out.append('\"');
+ }
+ }
+
+ appendCloseTag(out, element);
+}
+
+void StyledMarkupAccumulator::removeExteriorStyles(CSSMutableStyleDeclaration* style)
+{
+ style->removeProperty(CSSPropertyFloat);
+}
+
static Node* serializeNodes(StyledMarkupAccumulator& accumulator, Node* startNode, Node* pastEnd)
{
Vector<Node*> ancestorsToClose;
@@ -896,6 +856,9 @@ static Node* ancestorToRetainStructureAndAppearance(Node* commonAncestor)
{
Node* commonAncestorBlock = enclosingBlock(commonAncestor);
+ if (!commonAncestorBlock)
+ return 0;
+
if (commonAncestorBlock->hasTagName(tbodyTag) || commonAncestorBlock->hasTagName(trTag)) {
Node* table = commonAncestorBlock->parentNode();
while (table && !table->hasTagName(tableTag))
@@ -932,6 +895,29 @@ static bool propertyMissingOrEqualToNone(CSSStyleDeclaration* style, int propert
return static_cast<CSSPrimitiveValue*>(value.get())->getIdent() == CSSValueNone;
}
+static bool needInterchangeNewlineAfter(const VisiblePosition& v)
+{
+ VisiblePosition next = v.next();
+ Node* upstreamNode = next.deepEquivalent().upstream().node();
+ Node* downstreamNode = v.deepEquivalent().downstream().node();
+ // Add an interchange newline if a paragraph break is selected and a br won't already be added to the markup to represent it.
+ return isEndOfParagraph(v) && isStartOfParagraph(next) && !(upstreamNode->hasTagName(brTag) && upstreamNode == downstreamNode);
+}
+
+static PassRefPtr<CSSMutableStyleDeclaration> styleFromMatchedRulesAndInlineDecl(const Node* node)
+{
+ if (!node->isHTMLElement())
+ return 0;
+
+ // FIXME: Having to const_cast here is ugly, but it is quite a bit of work to untangle
+ // the non-const-ness of styleFromMatchedRulesForElement.
+ HTMLElement* element = const_cast<HTMLElement*>(static_cast<const HTMLElement*>(node));
+ RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesForElement(element);
+ RefPtr<CSSMutableStyleDeclaration> inlineStyleDecl = element->getInlineStyleDecl();
+ style->merge(inlineStyleDecl.get());
+ return style.release();
+}
+
static bool isElementPresentational(const Node* node)
{
if (node->hasTagName(uTag) || node->hasTagName(sTag) || node->hasTagName(strikeTag)
@@ -996,6 +982,23 @@ static Node* highestAncestorToWrapMarkup(const Range* range, Node* fullySelected
return specialCommonAncestor;
}
+static void removeEnclosingMailBlockquoteStyle(CSSMutableStyleDeclaration* style, Node* node)
+{
+ Node* blockquote = nearestMailBlockquote(node);
+ if (!blockquote || !blockquote->parentNode())
+ return;
+
+ removeStylesAddedByNode(style, blockquote);
+}
+
+static void removeDefaultStyles(CSSMutableStyleDeclaration* style, Document* document)
+{
+ if (!document || !document->documentElement())
+ return;
+
+ prepareEditingStyleToApplyAt(style, Position(document->documentElement(), 0));
+}
+
// FIXME: Shouldn't we omit style info when annotate == DoNotAnnotateForInterchange?
// FIXME: At least, annotation and style info should probably not be included in range.markupString()
String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterchange shouldAnnotate, bool convertBlocksToInlines, EAbsoluteURLs shouldResolveURLs)