diff options
Diffstat (limited to 'WebCore/editing')
-rw-r--r-- | WebCore/editing/ApplyStyleCommand.cpp | 70 | ||||
-rw-r--r-- | WebCore/editing/ApplyStyleCommand.h | 7 | ||||
-rw-r--r-- | WebCore/editing/DeleteButton.cpp | 12 | ||||
-rw-r--r-- | WebCore/editing/DeleteSelectionCommand.cpp | 2 | ||||
-rw-r--r-- | WebCore/editing/Editor.cpp | 113 | ||||
-rw-r--r-- | WebCore/editing/Editor.h | 3 | ||||
-rw-r--r-- | WebCore/editing/EditorCommand.cpp | 11 | ||||
-rw-r--r-- | WebCore/editing/InsertLineBreakCommand.cpp | 2 | ||||
-rw-r--r-- | WebCore/editing/InsertTextCommand.cpp | 2 | ||||
-rw-r--r-- | WebCore/editing/RemoveFormatCommand.cpp | 2 | ||||
-rw-r--r-- | WebCore/editing/ReplaceSelectionCommand.cpp | 4 | ||||
-rw-r--r-- | WebCore/editing/SelectionController.cpp | 240 | ||||
-rw-r--r-- | WebCore/editing/SelectionController.h | 40 | ||||
-rw-r--r-- | WebCore/editing/TextIterator.cpp | 26 | ||||
-rw-r--r-- | WebCore/editing/TextIterator.h | 23 | ||||
-rw-r--r-- | WebCore/editing/TypingCommand.cpp | 6 | ||||
-rw-r--r-- | WebCore/editing/mac/EditorMac.mm | 43 | ||||
-rw-r--r-- | WebCore/editing/markup.cpp | 491 |
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) |