diff options
author | Steve Block <steveblock@google.com> | 2010-02-02 14:57:50 +0000 |
---|---|---|
committer | Steve Block <steveblock@google.com> | 2010-02-04 15:06:55 +0000 |
commit | d0825bca7fe65beaee391d30da42e937db621564 (patch) | |
tree | 7461c49eb5844ffd1f35d1ba2c8b7584c1620823 /WebCore/editing | |
parent | 3db770bd97c5a59b6c7574ca80a39e5a51c1defd (diff) | |
download | external_webkit-d0825bca7fe65beaee391d30da42e937db621564.zip external_webkit-d0825bca7fe65beaee391d30da42e937db621564.tar.gz external_webkit-d0825bca7fe65beaee391d30da42e937db621564.tar.bz2 |
Merge webkit.org at r54127 : Initial merge by git
Change-Id: Ib661abb595522f50ea406f72d3a0ce17f7193c82
Diffstat (limited to 'WebCore/editing')
-rw-r--r-- | WebCore/editing/ApplyStyleCommand.cpp | 12 | ||||
-rw-r--r-- | WebCore/editing/CompositeEditCommand.cpp | 12 | ||||
-rw-r--r-- | WebCore/editing/DeleteButtonController.cpp | 8 | ||||
-rw-r--r-- | WebCore/editing/DeleteSelectionCommand.cpp | 11 | ||||
-rw-r--r-- | WebCore/editing/Editor.cpp | 9 | ||||
-rw-r--r-- | WebCore/editing/EditorCommand.cpp | 5 | ||||
-rw-r--r-- | WebCore/editing/IndentOutdentCommand.cpp | 7 | ||||
-rw-r--r-- | WebCore/editing/ReplaceSelectionCommand.cpp | 11 | ||||
-rw-r--r-- | WebCore/editing/SelectionController.cpp | 313 | ||||
-rw-r--r-- | WebCore/editing/SelectionController.h | 82 | ||||
-rw-r--r-- | WebCore/editing/TextIterator.cpp | 317 | ||||
-rw-r--r-- | WebCore/editing/TypingCommand.cpp | 4 | ||||
-rw-r--r-- | WebCore/editing/VisibleSelection.cpp | 2 | ||||
-rw-r--r-- | WebCore/editing/VisibleSelection.h | 6 | ||||
-rw-r--r-- | WebCore/editing/gtk/SelectionControllerGtk.cpp | 15 | ||||
-rw-r--r-- | WebCore/editing/htmlediting.cpp | 12 | ||||
-rw-r--r-- | WebCore/editing/htmlediting.h | 1 | ||||
-rw-r--r-- | WebCore/editing/mac/SelectionControllerMac.mm | 6 | ||||
-rw-r--r-- | WebCore/editing/markup.cpp | 31 | ||||
-rw-r--r-- | WebCore/editing/markup.h | 5 |
20 files changed, 678 insertions, 191 deletions
diff --git a/WebCore/editing/ApplyStyleCommand.cpp b/WebCore/editing/ApplyStyleCommand.cpp index 7a8f025..1c739ec 100644 --- a/WebCore/editing/ApplyStyleCommand.cpp +++ b/WebCore/editing/ApplyStyleCommand.cpp @@ -137,7 +137,7 @@ void StyleChange::reconcileTextDecorationProperties(CSSMutableStyleDeclaration* textDecoration = textDecorationsInEffect; } - // If text-decration is set to "none", remove the property because we don't want to add redundant "text-decoration: none". + // If text-decoration is set to "none", remove the property because we don't want to add redundant "text-decoration: none". if (textDecoration && !textDecoration->isValueList()) style->removeProperty(CSSPropertyTextDecoration); } @@ -250,7 +250,7 @@ void StyleChange::extractTextStyles(CSSMutableStyleDeclaration* style) else m_applyFontSize = "7"; } - // Huge quirk in Microsft Entourage is that they understand CSS font-size, but also write + // Huge quirk in Microsoft Entourage is that they understand CSS font-size, but also write // out legacy 1-7 values in font tags (I guess for mailers that are not CSS-savvy at all, // like Eudora). Yes, they write out *both*. We need to write out both as well. } @@ -302,7 +302,9 @@ static bool isEmptyFontTag(const Node *node) const Element *elem = static_cast<const Element *>(node); NamedNodeMap *map = elem->attributes(true); // true for read-only - return (!map || map->length() == 1) && elem->getAttribute(classAttr) == styleSpanClassString(); + if (!map) + return true; + return map->isEmpty() || (map->length() == 1 && elem->getAttribute(classAttr) == styleSpanClassString()); } static PassRefPtr<Element> createFontElement(Document* document) @@ -449,8 +451,8 @@ PassRefPtr<CSSMutableStyleDeclaration> editingStyleAtPosition(Position pos, Shou void prepareEditingStyleToApplyAt(CSSMutableStyleDeclaration* editingStyle, Position pos) { - // ReplaceSelectionCommand::handleStyleSpans() requiers that this function only removes the editing style. - // If this function was modified in the futureto delete all redundant properties, then add a boolean value to indicate + // ReplaceSelectionCommand::handleStyleSpans() requires that this function only removes the editing style. + // If this function was modified in the future to delete all redundant properties, then add a boolean value to indicate // which one of editingStyleAtPosition or computedStyle is called. RefPtr<CSSMutableStyleDeclaration> style = editingStyleAtPosition(pos); style->diff(editingStyle); diff --git a/WebCore/editing/CompositeEditCommand.cpp b/WebCore/editing/CompositeEditCommand.cpp index 2796690..e9b6971 100644 --- a/WebCore/editing/CompositeEditCommand.cpp +++ b/WebCore/editing/CompositeEditCommand.cpp @@ -771,9 +771,17 @@ void CompositeEditCommand::cloneParagraphUnderNewElement(Position& start, Positi // cloning all the siblings until end.node() is reached. if (start.node() != end.node() && !start.node()->isDescendantOf(end.node())) { + // If end is not a descendant of outerNode we need to + // find the first common ancestor and adjust the insertion + // point accordingly. + while (!end.node()->isDescendantOf(outerNode)) { + outerNode = outerNode->parentNode(); + topNode = topNode->parentNode(); + } + for (Node* n = start.node()->traverseNextSibling(outerNode); n; n = n->nextSibling()) { if (n->parentNode() != start.node()->parentNode()) - lastNode = topNode->firstChild(); + lastNode = topNode->lastChild(); RefPtr<Node> clonedNode = n->cloneNode(true); insertNodeAfter(clonedNode, lastNode); @@ -1082,7 +1090,7 @@ bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph() Position caretPos(caret.deepEquivalent()); // A line break is either a br or a preserved newline. - ASSERT(caretPos.node()->hasTagName(brTag) || caretPos.node()->isTextNode() && caretPos.node()->renderer()->style()->preserveNewline()); + ASSERT(caretPos.node()->hasTagName(brTag) || (caretPos.node()->isTextNode() && caretPos.node()->renderer()->style()->preserveNewline())); if (caretPos.node()->hasTagName(brTag)) { Position beforeBR(positionInParentBeforeNode(caretPos.node())); diff --git a/WebCore/editing/DeleteButtonController.cpp b/WebCore/editing/DeleteButtonController.cpp index c154426..d999f84 100644 --- a/WebCore/editing/DeleteButtonController.cpp +++ b/WebCore/editing/DeleteButtonController.cpp @@ -66,7 +66,7 @@ static bool isDeletableElement(const Node* node) if (!node || !node->isHTMLElement() || !node->inDocument() || !node->isContentEditable()) return false; - // In general we want to only draw the UI arround object of a certain area, but we still keep the min width/height to + // In general we want to only draw the UI around object of a certain area, but we still keep the min width/height to // make sure we don't end up with very thin or very short elements getting the UI. const int minimumArea = 2500; const int minimumWidth = 48; @@ -187,7 +187,7 @@ void DeleteButtonController::respondToChangedSelection(const VisibleSelection& o void DeleteButtonController::createDeletionUI() { RefPtr<HTMLDivElement> container = new HTMLDivElement(divTag, m_target->document()); - container->setAttribute(idAttr, containerElementIdentifier); + container->setAttribute(container->idAttributeName(), containerElementIdentifier); CSSMutableStyleDeclaration* style = container->getInlineStyleDecl(); style->setProperty(CSSPropertyWebkitUserDrag, CSSValueNone); @@ -202,7 +202,7 @@ void DeleteButtonController::createDeletionUI() style->setProperty(CSSPropertyLeft, "0"); RefPtr<HTMLDivElement> outline = new HTMLDivElement(divTag, m_target->document()); - outline->setAttribute(idAttr, outlineElementIdentifier); + outline->setAttribute(outline->idAttributeName(), outlineElementIdentifier); const int borderWidth = 4; const int borderRadius = 6; @@ -225,7 +225,7 @@ void DeleteButtonController::createDeletionUI() return; RefPtr<DeleteButton> button = new DeleteButton(m_target->document()); - button->setAttribute(idAttr, buttonElementIdentifier); + button->setAttribute(button->idAttributeName(), buttonElementIdentifier); const int buttonWidth = 30; const int buttonHeight = 30; diff --git a/WebCore/editing/DeleteSelectionCommand.cpp b/WebCore/editing/DeleteSelectionCommand.cpp index 9e4ba29..d3d9cc9 100644 --- a/WebCore/editing/DeleteSelectionCommand.cpp +++ b/WebCore/editing/DeleteSelectionCommand.cpp @@ -121,7 +121,7 @@ void DeleteSelectionCommand::initializeStartEnd(Position& start, Position& end) else if (end.node()->hasTagName(hrTag)) end = Position(end.node(), 1); - // FIXME: This is only used so that moveParagraphs can avoid the bugs in special element expanion. + // FIXME: This is only used so that moveParagraphs can avoid the bugs in special element expansion. if (!m_expandForSpecialElements) return; @@ -589,10 +589,11 @@ void DeleteSelectionCommand::mergeParagraphs() // The rule for merging into an empty block is: only do so if its farther to the right. // FIXME: Consider RTL. if (!m_startsAtEmptyLine && isStartOfParagraph(mergeDestination) && startOfParagraphToMove.absoluteCaretBounds().x() > mergeDestination.absoluteCaretBounds().x()) { - ASSERT(mergeDestination.deepEquivalent().downstream().node()->hasTagName(brTag)); - removeNodeAndPruneAncestors(mergeDestination.deepEquivalent().downstream().node()); - m_endingPosition = startOfParagraphToMove.deepEquivalent(); - return; + if (mergeDestination.deepEquivalent().downstream().node()->hasTagName(brTag)) { + removeNodeAndPruneAncestors(mergeDestination.deepEquivalent().downstream().node()); + m_endingPosition = startOfParagraphToMove.deepEquivalent(); + return; + } } // Block images, tables and horizontal rules cannot be made inline with content at mergeDestination. If there is diff --git a/WebCore/editing/Editor.cpp b/WebCore/editing/Editor.cpp index 0744fd6..5798452 100644 --- a/WebCore/editing/Editor.cpp +++ b/WebCore/editing/Editor.cpp @@ -1077,6 +1077,8 @@ void Editor::paste() void Editor::pasteAsPlainText() { + if (tryDHTMLPaste()) + return; if (!canPaste()) return; pasteAsPlainTextWithPasteboard(Pasteboard::generalPasteboard()); @@ -1391,8 +1393,11 @@ void Editor::confirmComposition(const String& text, bool preserveSelection) insertText(text, 0); - if (preserveSelection) + if (preserveSelection) { m_frame->selection()->setSelection(oldSelection, false, false); + // An open typing command that disagrees about current selection would cause issues with typing later on. + TypingCommand::closeTyping(m_lastEditCommand.get()); + } setIgnoreCompositionSelectionChange(false); } @@ -1497,7 +1502,7 @@ void Editor::learnSpelling() if (!client()) return; - // FIXME: We don't call this on the Mac, and it should remove misppelling markers around the + // FIXME: We don't call this on the Mac, and it should remove misspelling markers around the // learned word, see <rdar://problem/5396072>. String text = frame()->selectedText(); diff --git a/WebCore/editing/EditorCommand.cpp b/WebCore/editing/EditorCommand.cpp index 3379b3c..6a9e10f 100644 --- a/WebCore/editing/EditorCommand.cpp +++ b/WebCore/editing/EditorCommand.cpp @@ -32,6 +32,7 @@ #include "CSSMutableStyleDeclaration.h" #include "CSSPropertyNames.h" #include "CSSValueKeywords.h" +#include "Chrome.h" #include "CreateLinkCommand.h" #include "DocumentFragment.h" #include "Editor.h" @@ -259,7 +260,7 @@ static int verticalScrollDistance(Frame* frame) if (!(style->overflowY() == OSCROLL || style->overflowY() == OAUTO || renderer->isTextArea())) return 0; int height = toRenderBox(renderer)->clientHeight(); - return max((height + 1) / 2, height - cAmountToKeepWhenPaging); + return max(height * cFractionToStepWhenPaging, 1.f); } static RefPtr<Range> unionDOMRanges(Range* a, Range* b) @@ -471,7 +472,7 @@ static bool executeInsertHorizontalRule(Frame* frame, Event*, EditorCommandSourc { RefPtr<HTMLHRElement> hr = new HTMLHRElement(hrTag, frame->document()); if (!value.isEmpty()) - hr->setAttribute(idAttr, value); + hr->setAttribute(hr->idAttributeName(), value); return executeInsertNode(frame, hr.release()); } diff --git a/WebCore/editing/IndentOutdentCommand.cpp b/WebCore/editing/IndentOutdentCommand.cpp index 5e6f339..0f3975b 100644 --- a/WebCore/editing/IndentOutdentCommand.cpp +++ b/WebCore/editing/IndentOutdentCommand.cpp @@ -76,7 +76,12 @@ bool IndentOutdentCommand::tryIndentingAsListItem(const VisiblePosition& endOfCu return false; // Find the list item enclosing the current paragraph - Element* selectedListItem = static_cast<Element*>(enclosingBlock(endOfCurrentParagraph.deepEquivalent().node())); + Element* selectedListItem = static_cast<Element*>(enclosingBlock(lastNodeInSelectedParagraph)); + // FIXME: enclosingBlock shouldn't return the passed in element. See the + // comment on the function about how to fix rather than having to adjust here. + if (selectedListItem == lastNodeInSelectedParagraph) + selectedListItem = static_cast<Element*>(enclosingBlock(lastNodeInSelectedParagraph->parentNode())); + // FIXME: we need to deal with the case where there is no li (malformed HTML) if (!selectedListItem->hasTagName(liTag)) return false; diff --git a/WebCore/editing/ReplaceSelectionCommand.cpp b/WebCore/editing/ReplaceSelectionCommand.cpp index b40dab2..85a4471 100644 --- a/WebCore/editing/ReplaceSelectionCommand.cpp +++ b/WebCore/editing/ReplaceSelectionCommand.cpp @@ -877,6 +877,8 @@ void ReplaceSelectionCommand::doApply() if (!refNode->inDocument()) return; + bool plainTextFragment = isPlainTextMarkup(refNode.get()); + while (node) { Node* next = node->nextSibling(); fragment.removeNode(node); @@ -887,6 +889,8 @@ void ReplaceSelectionCommand::doApply() return; refNode = node; + if (node && plainTextFragment) + plainTextFragment = isPlainTextMarkup(node.get()); node = next; } @@ -913,7 +917,7 @@ void ReplaceSelectionCommand::doApply() bool interchangeNewlineAtEnd = fragment.hasInterchangeNewlineAtEnd(); - if (shouldRemoveEndBR(endBR, originalVisPosBeforeEndBR)) + if (endBR && (plainTextFragment || shouldRemoveEndBR(endBR, originalVisPosBeforeEndBR))) removeNodeAndPruneAncestors(endBR); // Determine whether or not we should merge the end of inserted content with what's after it before we do @@ -1020,6 +1024,11 @@ void ReplaceSelectionCommand::doApply() } } + // If we are dealing with a fragment created from plain text + // no style matching is necessary. + if (plainTextFragment) + m_matchStyle = false; + completeHTMLReplacement(lastPositionToSelect); } diff --git a/WebCore/editing/SelectionController.cpp b/WebCore/editing/SelectionController.cpp index af89ccb..5b2d0d0 100644 --- a/WebCore/editing/SelectionController.cpp +++ b/WebCore/editing/SelectionController.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2004, 2008, 2009, 2010 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -47,6 +47,7 @@ #include "Range.h" #include "RenderTheme.h" #include "RenderView.h" +#include "Settings.h" #include "TextIterator.h" #include "TypingCommand.h" #include "htmlediting.h" @@ -64,12 +65,15 @@ const int NoXPosForVerticalArrowNavigation = INT_MIN; SelectionController::SelectionController(Frame* frame, bool isDragCaretController) : m_frame(frame) , m_xPosForVerticalArrowNavigation(NoXPosForVerticalArrowNavigation) + , m_caretBlinkTimer(this, &SelectionController::caretBlinkTimerFired) , m_needsLayout(true) , m_absCaretBoundsDirty(true) , m_lastChangeWasHorizontalExtension(false) , m_isDragCaretController(isDragCaretController) , m_isCaretBlinkingSuspended(false) , m_focused(frame && frame->page() && frame->page()->focusController()->focusedFrame() == frame) + , m_caretVisible(isDragCaretController) + , m_caretPaint(true) { } @@ -105,13 +109,13 @@ void SelectionController::setSelection(const VisibleSelection& s, bool closeTypi if (m_isDragCaretController) { invalidateCaretRect(); - m_sel = s; + m_selection = s; m_needsLayout = true; invalidateCaretRect(); return; } if (!m_frame) { - m_sel = s; + m_selection = s; return; } @@ -133,19 +137,20 @@ void SelectionController::setSelection(const VisibleSelection& s, bool closeTypi if (clearTypingStyle) m_frame->clearTypingStyle(); - if (m_sel == s) + if (m_selection == s) return; - VisibleSelection oldSelection = m_sel; + VisibleSelection oldSelection = m_selection; - m_sel = s; + m_selection = s; m_needsLayout = true; if (!s.isNone()) m_frame->setFocusedNodeIfNeeded(); - m_frame->selectionLayoutChanged(); + updateAppearance(); + // Always clear the x position used for vertical arrow navigation. // It will be restored by the vertical arrow navigation code if necessary. m_xPosForVerticalArrowNavigation = NoXPosForVerticalArrowNavigation; @@ -183,10 +188,10 @@ void SelectionController::nodeWillBeRemoved(Node *node) if (node && highestAncestor(node)->nodeType() == Node::DOCUMENT_FRAGMENT_NODE) return; - bool baseRemoved = removingNodeRemovesPosition(node, m_sel.base()); - bool extentRemoved = removingNodeRemovesPosition(node, m_sel.extent()); - bool startRemoved = removingNodeRemovesPosition(node, m_sel.start()); - bool endRemoved = removingNodeRemovesPosition(node, m_sel.end()); + bool baseRemoved = removingNodeRemovesPosition(node, m_selection.base()); + bool extentRemoved = removingNodeRemovesPosition(node, m_selection.extent()); + bool startRemoved = removingNodeRemovesPosition(node, m_selection.start()); + bool endRemoved = removingNodeRemovesPosition(node, m_selection.end()); bool clearRenderTreeSelection = false; bool clearDOMTreeSelection = false; @@ -200,12 +205,12 @@ void SelectionController::nodeWillBeRemoved(Node *node) // Change the base and extent to the start and end, but don't re-validate the // selection, since doing so could move the start and end into the node // that is about to be removed. - if (m_sel.isBaseFirst()) - m_sel.setWithoutValidation(m_sel.start(), m_sel.end()); + if (m_selection.isBaseFirst()) + m_selection.setWithoutValidation(m_selection.start(), m_selection.end()); else - m_sel.setWithoutValidation(m_sel.end(), m_sel.start()); + m_selection.setWithoutValidation(m_selection.end(), m_selection.start()); // FIXME: This could be more efficient if we had an isNodeInRange function on Ranges. - } else if (comparePositions(m_sel.start(), Position(node, 0)) == -1 && comparePositions(m_sel.end(), Position(node, 0)) == 1) { + } else if (comparePositions(m_selection.start(), Position(node, 0)) == -1 && comparePositions(m_selection.end(), Position(node, 0)) == 1) { // If we did nothing here, when this node's renderer was destroyed, the rect that it // occupied would be invalidated, but, selection gaps that change as a result of // the removal wouldn't be invalidated. @@ -214,7 +219,7 @@ void SelectionController::nodeWillBeRemoved(Node *node) } if (clearRenderTreeSelection) { - RefPtr<Document> document = m_sel.start().node()->document(); + RefPtr<Document> document = m_selection.start().node()->document(); document->updateStyleIfNeeded(); if (RenderView* view = toRenderView(document->renderer())) view->clearSelection(); @@ -231,26 +236,26 @@ void SelectionController::willBeModified(EAlteration alter, EDirection direction if (m_lastChangeWasHorizontalExtension) return; - Position start = m_sel.start(); - Position end = m_sel.end(); + Position start = m_selection.start(); + Position end = m_selection.end(); // FIXME: This is probably not correct for right and left when the direction is RTL. switch (direction) { case RIGHT: case FORWARD: - m_sel.setBase(start); - m_sel.setExtent(end); + m_selection.setBase(start); + m_selection.setExtent(end); break; case LEFT: case BACKWARD: - m_sel.setBase(end); - m_sel.setExtent(start); + m_selection.setBase(end); + m_selection.setExtent(start); break; } } TextDirection SelectionController::directionOfEnclosingBlock() { - Node* n = m_sel.extent().node(); + Node* n = m_selection.extent().node(); Node* enclosingBlockNode = enclosingBlock(n); if (!enclosingBlockNode) return LTR; @@ -262,7 +267,7 @@ TextDirection SelectionController::directionOfEnclosingBlock() VisiblePosition SelectionController::modifyExtendingRight(TextGranularity granularity) { - VisiblePosition pos(m_sel.extent(), m_sel.affinity()); + VisiblePosition pos(m_selection.extent(), m_selection.affinity()); // The difference between modifyExtendingRight and modifyExtendingForward is: // modifyExtendingForward always extends forward logically. @@ -297,7 +302,7 @@ VisiblePosition SelectionController::modifyExtendingRight(TextGranularity granul VisiblePosition SelectionController::modifyExtendingForward(TextGranularity granularity) { - VisiblePosition pos(m_sel.extent(), m_sel.affinity()); + VisiblePosition pos(m_selection.extent(), m_selection.affinity()); switch (granularity) { case CharacterGranularity: pos = pos.next(true); @@ -315,16 +320,16 @@ VisiblePosition SelectionController::modifyExtendingForward(TextGranularity gran pos = nextParagraphPosition(pos, xPosForVerticalArrowNavigation(EXTENT)); break; case SentenceBoundary: - pos = endOfSentence(VisiblePosition(m_sel.end(), m_sel.affinity())); + pos = endOfSentence(VisiblePosition(m_selection.end(), m_selection.affinity())); break; case LineBoundary: - pos = logicalEndOfLine(VisiblePosition(m_sel.end(), m_sel.affinity())); + pos = logicalEndOfLine(VisiblePosition(m_selection.end(), m_selection.affinity())); break; case ParagraphBoundary: - pos = endOfParagraph(VisiblePosition(m_sel.end(), m_sel.affinity())); + pos = endOfParagraph(VisiblePosition(m_selection.end(), m_selection.affinity())); break; case DocumentBoundary: - pos = VisiblePosition(m_sel.end(), m_sel.affinity()); + pos = VisiblePosition(m_selection.end(), m_selection.affinity()); if (isEditablePosition(pos.deepEquivalent())) pos = endOfEditableContent(pos); else @@ -341,9 +346,9 @@ VisiblePosition SelectionController::modifyMovingRight(TextGranularity granulari switch (granularity) { case CharacterGranularity: if (isRange()) - pos = VisiblePosition(m_sel.end(), m_sel.affinity()); + pos = VisiblePosition(m_selection.end(), m_selection.affinity()); else - pos = VisiblePosition(m_sel.extent(), m_sel.affinity()).right(true); + pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).right(true); break; case WordGranularity: case SentenceGranularity: @@ -367,38 +372,38 @@ VisiblePosition SelectionController::modifyMovingForward(TextGranularity granula switch (granularity) { case CharacterGranularity: if (isRange()) - pos = VisiblePosition(m_sel.end(), m_sel.affinity()); + pos = VisiblePosition(m_selection.end(), m_selection.affinity()); else - pos = VisiblePosition(m_sel.extent(), m_sel.affinity()).next(true); + pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).next(true); break; case WordGranularity: - pos = nextWordPosition(VisiblePosition(m_sel.extent(), m_sel.affinity())); + pos = nextWordPosition(VisiblePosition(m_selection.extent(), m_selection.affinity())); break; case SentenceGranularity: - pos = nextSentencePosition(VisiblePosition(m_sel.extent(), m_sel.affinity())); + pos = nextSentencePosition(VisiblePosition(m_selection.extent(), m_selection.affinity())); break; case LineGranularity: { // down-arrowing from a range selection that ends at the start of a line needs // to leave the selection at that line start (no need to call nextLinePosition!) - pos = VisiblePosition(m_sel.end(), m_sel.affinity()); + pos = VisiblePosition(m_selection.end(), m_selection.affinity()); if (!isRange() || !isStartOfLine(pos)) pos = nextLinePosition(pos, xPosForVerticalArrowNavigation(START)); break; } case ParagraphGranularity: - pos = nextParagraphPosition(VisiblePosition(m_sel.end(), m_sel.affinity()), xPosForVerticalArrowNavigation(START)); + pos = nextParagraphPosition(VisiblePosition(m_selection.end(), m_selection.affinity()), xPosForVerticalArrowNavigation(START)); break; case SentenceBoundary: - pos = endOfSentence(VisiblePosition(m_sel.end(), m_sel.affinity())); + pos = endOfSentence(VisiblePosition(m_selection.end(), m_selection.affinity())); break; case LineBoundary: - pos = logicalEndOfLine(VisiblePosition(m_sel.end(), m_sel.affinity())); + pos = logicalEndOfLine(VisiblePosition(m_selection.end(), m_selection.affinity())); break; case ParagraphBoundary: - pos = endOfParagraph(VisiblePosition(m_sel.end(), m_sel.affinity())); + pos = endOfParagraph(VisiblePosition(m_selection.end(), m_selection.affinity())); break; case DocumentBoundary: - pos = VisiblePosition(m_sel.end(), m_sel.affinity()); + pos = VisiblePosition(m_selection.end(), m_selection.affinity()); if (isEditablePosition(pos.deepEquivalent())) pos = endOfEditableContent(pos); else @@ -411,7 +416,7 @@ VisiblePosition SelectionController::modifyMovingForward(TextGranularity granula VisiblePosition SelectionController::modifyExtendingLeft(TextGranularity granularity) { - VisiblePosition pos(m_sel.extent(), m_sel.affinity()); + VisiblePosition pos(m_selection.extent(), m_selection.affinity()); // The difference between modifyExtendingLeft and modifyExtendingBackward is: // modifyExtendingBackward always extends backward logically. @@ -445,7 +450,7 @@ VisiblePosition SelectionController::modifyExtendingLeft(TextGranularity granula VisiblePosition SelectionController::modifyExtendingBackward(TextGranularity granularity) { - VisiblePosition pos(m_sel.extent(), m_sel.affinity()); + VisiblePosition pos(m_selection.extent(), m_selection.affinity()); // Extending a selection backward by word or character from just after a table selects // the table. This "makes sense" from the user perspective, esp. when deleting. @@ -468,16 +473,16 @@ VisiblePosition SelectionController::modifyExtendingBackward(TextGranularity gra pos = previousParagraphPosition(pos, xPosForVerticalArrowNavigation(EXTENT)); break; case SentenceBoundary: - pos = startOfSentence(VisiblePosition(m_sel.start(), m_sel.affinity())); + pos = startOfSentence(VisiblePosition(m_selection.start(), m_selection.affinity())); break; case LineBoundary: - pos = logicalStartOfLine(VisiblePosition(m_sel.start(), m_sel.affinity())); + pos = logicalStartOfLine(VisiblePosition(m_selection.start(), m_selection.affinity())); break; case ParagraphBoundary: - pos = startOfParagraph(VisiblePosition(m_sel.start(), m_sel.affinity())); + pos = startOfParagraph(VisiblePosition(m_selection.start(), m_selection.affinity())); break; case DocumentBoundary: - pos = VisiblePosition(m_sel.start(), m_sel.affinity()); + pos = VisiblePosition(m_selection.start(), m_selection.affinity()); if (isEditablePosition(pos.deepEquivalent())) pos = startOfEditableContent(pos); else @@ -493,9 +498,9 @@ VisiblePosition SelectionController::modifyMovingLeft(TextGranularity granularit switch (granularity) { case CharacterGranularity: if (isRange()) - pos = VisiblePosition(m_sel.start(), m_sel.affinity()); + pos = VisiblePosition(m_selection.start(), m_selection.affinity()); else - pos = VisiblePosition(m_sel.extent(), m_sel.affinity()).left(true); + pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).left(true); break; case WordGranularity: case SentenceGranularity: @@ -518,33 +523,33 @@ VisiblePosition SelectionController::modifyMovingBackward(TextGranularity granul switch (granularity) { case CharacterGranularity: if (isRange()) - pos = VisiblePosition(m_sel.start(), m_sel.affinity()); + pos = VisiblePosition(m_selection.start(), m_selection.affinity()); else - pos = VisiblePosition(m_sel.extent(), m_sel.affinity()).previous(true); + pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).previous(true); break; case WordGranularity: - pos = previousWordPosition(VisiblePosition(m_sel.extent(), m_sel.affinity())); + pos = previousWordPosition(VisiblePosition(m_selection.extent(), m_selection.affinity())); break; case SentenceGranularity: - pos = previousSentencePosition(VisiblePosition(m_sel.extent(), m_sel.affinity())); + pos = previousSentencePosition(VisiblePosition(m_selection.extent(), m_selection.affinity())); break; case LineGranularity: - pos = previousLinePosition(VisiblePosition(m_sel.start(), m_sel.affinity()), xPosForVerticalArrowNavigation(START)); + pos = previousLinePosition(VisiblePosition(m_selection.start(), m_selection.affinity()), xPosForVerticalArrowNavigation(START)); break; case ParagraphGranularity: - pos = previousParagraphPosition(VisiblePosition(m_sel.start(), m_sel.affinity()), xPosForVerticalArrowNavigation(START)); + pos = previousParagraphPosition(VisiblePosition(m_selection.start(), m_selection.affinity()), xPosForVerticalArrowNavigation(START)); break; case SentenceBoundary: - pos = startOfSentence(VisiblePosition(m_sel.start(), m_sel.affinity())); + pos = startOfSentence(VisiblePosition(m_selection.start(), m_selection.affinity())); break; case LineBoundary: - pos = logicalStartOfLine(VisiblePosition(m_sel.start(), m_sel.affinity())); + pos = logicalStartOfLine(VisiblePosition(m_selection.start(), m_selection.affinity())); break; case ParagraphBoundary: - pos = startOfParagraph(VisiblePosition(m_sel.start(), m_sel.affinity())); + pos = startOfParagraph(VisiblePosition(m_selection.start(), m_selection.affinity())); break; case DocumentBoundary: - pos = VisiblePosition(m_sel.start(), m_sel.affinity()); + pos = VisiblePosition(m_selection.start(), m_selection.affinity()); if (isEditablePosition(pos.deepEquivalent())) pos = startOfEditableContent(pos); else @@ -558,7 +563,7 @@ bool SelectionController::modify(EAlteration alter, EDirection dir, TextGranular { if (userTriggered) { SelectionController trialSelectionController; - trialSelectionController.setSelection(m_sel); + trialSelectionController.setSelection(m_selection); trialSelectionController.setLastChangeWasHorizontalExtension(m_lastChangeWasHorizontalExtension); trialSelectionController.modify(alter, dir, granularity, false); @@ -654,7 +659,7 @@ bool SelectionController::modify(EAlteration alter, int verticalDistance, bool u if (userTriggered) { SelectionController trialSelectionController; - trialSelectionController.setSelection(m_sel); + trialSelectionController.setSelection(m_selection); trialSelectionController.setLastChangeWasHorizontalExtension(m_lastChangeWasHorizontalExtension); trialSelectionController.modify(alter, verticalDistance, false); @@ -673,14 +678,14 @@ bool SelectionController::modify(EAlteration alter, int verticalDistance, bool u int xPos = 0; switch (alter) { case MOVE: - pos = VisiblePosition(up ? m_sel.start() : m_sel.end(), m_sel.affinity()); + pos = VisiblePosition(up ? m_selection.start() : m_selection.end(), m_selection.affinity()); xPos = xPosForVerticalArrowNavigation(up ? START : END); - m_sel.setAffinity(up ? UPSTREAM : DOWNSTREAM); + m_selection.setAffinity(up ? UPSTREAM : DOWNSTREAM); break; case EXTEND: - pos = VisiblePosition(m_sel.extent(), m_sel.affinity()); + pos = VisiblePosition(m_selection.extent(), m_selection.affinity()); xPos = xPosForVerticalArrowNavigation(EXTENT); - m_sel.setAffinity(DOWNSTREAM); + m_selection.setAffinity(DOWNSTREAM); break; } @@ -735,7 +740,7 @@ bool SelectionController::expandUsingGranularity(TextGranularity granularity) if (isNone()) return false; - m_sel.expandUsingGranularity(granularity); + m_selection.expandUsingGranularity(granularity); m_needsLayout = true; return true; } @@ -750,16 +755,16 @@ int SelectionController::xPosForVerticalArrowNavigation(EPositionType type) Position pos; switch (type) { case START: - pos = m_sel.start(); + pos = m_selection.start(); break; case END: - pos = m_sel.end(); + pos = m_selection.end(); break; case BASE: - pos = m_sel.base(); + pos = m_selection.base(); break; case EXTENT: - pos = m_sel.extent(); + pos = m_selection.extent(); break; } @@ -768,7 +773,7 @@ int SelectionController::xPosForVerticalArrowNavigation(EPositionType type) return x; if (m_xPosForVerticalArrowNavigation == NoXPosForVerticalArrowNavigation) { - VisiblePosition visiblePosition(pos, m_sel.affinity()); + VisiblePosition visiblePosition(pos, m_selection.affinity()); // VisiblePosition creation can fail here if a node containing the selection becomes visibility:hidden // after the selection is created and before this function is called. x = visiblePosition.isNotNull() ? visiblePosition.xOffsetForVerticalNavigation() : 0; @@ -787,22 +792,22 @@ void SelectionController::clear() void SelectionController::setBase(const VisiblePosition &pos, bool userTriggered) { - setSelection(VisibleSelection(pos.deepEquivalent(), m_sel.extent(), pos.affinity()), true, true, userTriggered); + setSelection(VisibleSelection(pos.deepEquivalent(), m_selection.extent(), pos.affinity()), true, true, userTriggered); } void SelectionController::setExtent(const VisiblePosition &pos, bool userTriggered) { - setSelection(VisibleSelection(m_sel.base(), pos.deepEquivalent(), pos.affinity()), true, true, userTriggered); + setSelection(VisibleSelection(m_selection.base(), pos.deepEquivalent(), pos.affinity()), true, true, userTriggered); } void SelectionController::setBase(const Position &pos, EAffinity affinity, bool userTriggered) { - setSelection(VisibleSelection(pos, m_sel.extent(), affinity), true, true, userTriggered); + setSelection(VisibleSelection(pos, m_selection.extent(), affinity), true, true, userTriggered); } void SelectionController::setExtent(const Position &pos, EAffinity affinity, bool userTriggered) { - setSelection(VisibleSelection(m_sel.base(), pos, affinity), true, true, userTriggered); + setSelection(VisibleSelection(m_selection.base(), pos, affinity), true, true, userTriggered); } void SelectionController::setNeedsLayout(bool flag) @@ -812,17 +817,17 @@ void SelectionController::setNeedsLayout(bool flag) void SelectionController::layout() { - if (isNone() || !m_sel.start().node()->inDocument() || !m_sel.end().node()->inDocument()) { + if (isNone() || !m_selection.start().node()->inDocument() || !m_selection.end().node()->inDocument()) { m_caretRect = IntRect(); return; } - m_sel.start().node()->document()->updateStyleIfNeeded(); + m_selection.start().node()->document()->updateStyleIfNeeded(); m_caretRect = IntRect(); if (isCaret()) { - VisiblePosition pos(m_sel.start(), m_sel.affinity()); + VisiblePosition pos(m_selection.start(), m_selection.affinity()); if (pos.isNotNull()) { ASSERT(pos.deepEquivalent().node()->renderer()); @@ -862,7 +867,7 @@ void SelectionController::layout() RenderObject* SelectionController::caretRenderer() const { - Node* node = m_sel.start().node(); + Node* node = m_selection.start().node(); if (!node) return 0; @@ -956,7 +961,7 @@ void SelectionController::invalidateCaretRect() if (!isCaret()) return; - Document* d = m_sel.start().node()->document(); + Document* d = m_selection.start().node()->document(); // recomputeCaretRect will always return false for the drag caret, // because its m_frame is always 0. @@ -981,28 +986,32 @@ void SelectionController::invalidateCaretRect() } } -void SelectionController::paintCaret(GraphicsContext* p, int tx, int ty, const IntRect& clipRect) +void SelectionController::paintCaret(GraphicsContext* context, int tx, int ty, const IntRect& clipRect) { - if (! m_sel.isCaret()) +#if ENABLE(TEXT_CARET) + if (!m_caretVisible) + return; + if (!m_caretPaint) + return; + if (!m_selection.isCaret()) return; - - if (m_needsLayout) - layout(); IntRect drawingRect = localCaretRect(); drawingRect.move(tx, ty); IntRect caret = intersection(drawingRect, clipRect); - if (!caret.isEmpty()) { - Color caretColor = Color::black; - ColorSpace colorSpace = DeviceColorSpace; - Element* element = rootEditableElement(); - if (element && element->renderer()) { - caretColor = element->renderer()->style()->color(); - colorSpace = element->renderer()->style()->colorSpace(); - } + if (caret.isEmpty()) + return; - p->fillRect(caret, caretColor, colorSpace); + Color caretColor = Color::black; + ColorSpace colorSpace = DeviceColorSpace; + Element* element = rootEditableElement(); + if (element && element->renderer()) { + caretColor = element->renderer()->style()->color(); + colorSpace = element->renderer()->style()->colorSpace(); } + + context->fillRect(caret, caretColor, colorSpace); +#endif } void SelectionController::debugRenderer(RenderObject *r, bool selected) const @@ -1023,10 +1032,10 @@ void SelectionController::debugRenderer(RenderObject *r, bool selected) const int textLength = text.length(); if (selected) { int offset = 0; - if (r->node() == m_sel.start().node()) - offset = m_sel.start().deprecatedEditingOffset(); - else if (r->node() == m_sel.end().node()) - offset = m_sel.end().deprecatedEditingOffset(); + if (r->node() == m_selection.start().node()) + offset = m_selection.start().deprecatedEditingOffset(); + else if (r->node() == m_selection.end().node()) + offset = m_selection.end().deprecatedEditingOffset(); int pos; InlineTextBox *box = textRenderer->findNextInlineTextBox(offset, pos); @@ -1100,11 +1109,11 @@ bool SelectionController::contains(const IntPoint& point) if (visiblePos.isNull()) return false; - if (m_sel.visibleStart().isNull() || m_sel.visibleEnd().isNull()) + if (m_selection.visibleStart().isNull() || m_selection.visibleEnd().isNull()) return false; - Position start(m_sel.visibleStart().deepEquivalent()); - Position end(m_sel.visibleEnd().deepEquivalent()); + Position start(m_selection.visibleStart().deepEquivalent()); + Position end(m_selection.visibleEnd().deepEquivalent()); Position p(visiblePos.deepEquivalent()); return comparePositions(start, p) <= 0 && comparePositions(p, end) <= 0; @@ -1170,7 +1179,7 @@ void SelectionController::selectAll() Node* root = 0; if (isContentEditable()) - root = highestEditableRoot(m_sel.start()); + root = highestEditableRoot(m_selection.start()); else { root = shadowTreeRootNode(); if (!root) @@ -1262,7 +1271,7 @@ void SelectionController::focusedOrActiveStateChanged() // Caret appears in the active frame. if (activeAndFocused) m_frame->setSelectionFromNone(); - m_frame->setCaretVisible(activeAndFocused); + setCaretVisible(activeAndFocused); // Update for caps lock state m_frame->eventHandler()->capsLockStateMayHaveChanged(); @@ -1301,16 +1310,110 @@ bool SelectionController::isFocusedAndActive() const return m_focused && m_frame->page() && m_frame->page()->focusController()->isActive(); } +void SelectionController::updateAppearance() +{ + ASSERT(!m_isDragCaretController); + +#if ENABLE(TEXT_CARET) + bool caretRectChanged = recomputeCaretRect(); + + bool caretBrowsing = m_frame->settings() && m_frame->settings()->caretBrowsingEnabled(); + bool shouldBlink = m_caretVisible + && isCaret() && (isContentEditable() || caretBrowsing); + + // If the caret moved, stop the blink timer so we can restart with a + // black caret in the new location. + if (caretRectChanged || !shouldBlink) + m_caretBlinkTimer.stop(); + + // Start blinking with a black caret. Be sure not to restart if we're + // already blinking in the right location. + if (shouldBlink && !m_caretBlinkTimer.isActive()) { + if (double blinkInterval = m_frame->page()->theme()->caretBlinkInterval()) + m_caretBlinkTimer.startRepeating(blinkInterval); + + if (!m_caretPaint) { + m_caretPaint = true; + invalidateCaretRect(); + } + } +#endif + + RenderView* view = m_frame->contentRenderer(); + if (!view) + return; + + VisibleSelection selection = this->selection(); + + if (!selection.isRange()) { + view->clearSelection(); + return; + } + + // Use the rightmost candidate for the start of the selection, and the leftmost candidate for the end of the selection. + // Example: foo <a>bar</a>. Imagine that a line wrap occurs after 'foo', and that 'bar' is selected. If we pass [foo, 3] + // as the start of the selection, the selection painting code will think that content on the line containing 'foo' is selected + // and will fill the gap before 'bar'. + Position startPos = selection.start(); + Position candidate = startPos.downstream(); + if (candidate.isCandidate()) + startPos = candidate; + Position endPos = selection.end(); + candidate = endPos.upstream(); + if (candidate.isCandidate()) + endPos = candidate; + + // We can get into a state where the selection endpoints map to the same VisiblePosition when a selection is deleted + // because we don't yet notify the SelectionController of text removal. + if (startPos.isNotNull() && endPos.isNotNull() && selection.visibleStart() != selection.visibleEnd()) { + RenderObject* startRenderer = startPos.node()->renderer(); + RenderObject* endRenderer = endPos.node()->renderer(); + view->setSelection(startRenderer, startPos.deprecatedEditingOffset(), endRenderer, endPos.deprecatedEditingOffset()); + } +} + +void SelectionController::setCaretVisible(bool flag) +{ + if (m_caretVisible == flag) + return; + clearCaretRectIfNeeded(); + m_caretVisible = flag; + updateAppearance(); +} + +void SelectionController::clearCaretRectIfNeeded() +{ +#if ENABLE(TEXT_CARET) + if (!m_caretPaint) + return; + m_caretPaint = false; + invalidateCaretRect(); +#endif +} + +void SelectionController::caretBlinkTimerFired(Timer<SelectionController>*) +{ +#if ENABLE(TEXT_CARET) + ASSERT(m_caretVisible); + ASSERT(isCaret()); + bool caretPaint = m_caretPaint; + if (isCaretBlinkingSuspended() && caretPaint) + return; + m_caretPaint = !caretPaint; + invalidateCaretRect(); +#endif +} + #ifndef NDEBUG void SelectionController::formatForDebugger(char* buffer, unsigned length) const { - m_sel.formatForDebugger(buffer, length); + m_selection.formatForDebugger(buffer, length); } void SelectionController::showTreeForThis() const { - m_sel.showTreeForThis(); + m_selection.showTreeForThis(); } #endif diff --git a/WebCore/editing/SelectionController.h b/WebCore/editing/SelectionController.h index 4a13a30..7cad435 100644 --- a/WebCore/editing/SelectionController.h +++ b/WebCore/editing/SelectionController.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004 Apple Computer, Inc. All rights reserved. + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -28,6 +28,7 @@ #include "IntRect.h" #include "Range.h" +#include "Timer.h" #include "VisibleSelection.h" #include <wtf/Noncopyable.h> @@ -45,10 +46,10 @@ public: SelectionController(Frame* = 0, bool isDragCaretController = false); - Element* rootEditableElement() const { return m_sel.rootEditableElement(); } - bool isContentEditable() const { return m_sel.isContentEditable(); } - bool isContentRichlyEditable() const { return m_sel.isContentRichlyEditable(); } - Node* shadowTreeRootNode() const { return m_sel.shadowTreeRootNode(); } + Element* rootEditableElement() const { return m_selection.rootEditableElement(); } + bool isContentEditable() const { return m_selection.isContentEditable(); } + bool isContentRichlyEditable() const { return m_selection.isContentRichlyEditable(); } + Node* shadowTreeRootNode() const { return m_selection.shadowTreeRootNode(); } void moveTo(const Range*, EAffinity, bool userTriggered = false); void moveTo(const VisiblePosition&, bool userTriggered = false); @@ -56,7 +57,7 @@ public: void moveTo(const Position&, EAffinity, bool userTriggered = false); void moveTo(const Position&, const Position&, EAffinity, bool userTriggered = false); - const VisibleSelection& selection() const { return m_sel; } + const VisibleSelection& selection() const { return m_selection; } void setSelection(const VisibleSelection&, bool closeTyping = true, bool clearTypingStyle = true, bool userTriggered = false); bool setSelectedRange(Range*, EAffinity, bool closeTyping); void selectAll(); @@ -67,9 +68,9 @@ public: bool contains(const IntPoint&); - VisibleSelection::SelectionType selectionType() const { return m_sel.selectionType(); } + VisibleSelection::SelectionType selectionType() const { return m_selection.selectionType(); } - EAffinity affinity() const { return m_sel.affinity(); } + EAffinity affinity() const { return m_selection.affinity(); } bool modify(EAlteration, EDirection, TextGranularity, bool userTriggered = false); bool modify(EAlteration, int verticalDistance, bool userTriggered = false); @@ -80,10 +81,10 @@ public: void setExtent(const VisiblePosition&, bool userTriggered = false); void setExtent(const Position&, EAffinity, bool userTriggered = false); - Position base() const { return m_sel.base(); } - Position extent() const { return m_sel.extent(); } - Position start() const { return m_sel.start(); } - Position end() const { return m_sel.end(); } + Position base() const { return m_selection.base(); } + Position extent() const { return m_selection.extent(); } + Position start() const { return m_selection.start(); } + Position end() const { return m_selection.end(); } // Return the renderer that is responsible for painting the caret (in the selection start node) RenderObject* caretRenderer() const; @@ -97,19 +98,21 @@ public: void setLastChangeWasHorizontalExtension(bool b) { m_lastChangeWasHorizontalExtension = b; } void willBeModified(EAlteration, EDirection); - bool isNone() const { return m_sel.isNone(); } - bool isCaret() const { return m_sel.isCaret(); } - bool isRange() const { return m_sel.isRange(); } - bool isCaretOrRange() const { return m_sel.isCaretOrRange(); } + bool isNone() const { return m_selection.isNone(); } + bool isCaret() const { return m_selection.isCaret(); } + bool isRange() const { return m_selection.isRange(); } + bool isCaretOrRange() const { return m_selection.isCaretOrRange(); } bool isInPasswordField() const; - bool isAll(StayInEditableContent stayInEditableContent = MustStayInEditableContent) const { return m_sel.isAll(stayInEditableContent); } + bool isAll(StayInEditableContent stayInEditableContent = MustStayInEditableContent) const { return m_selection.isAll(stayInEditableContent); } - PassRefPtr<Range> toNormalizedRange() const { return m_sel.toNormalizedRange(); } + PassRefPtr<Range> toNormalizedRange() const { return m_selection.toNormalizedRange(); } void debugRenderer(RenderObject*, bool selected) const; void nodeWillBeRemoved(Node*); + void setCaretVisible(bool = true); + void clearCaretRectIfNeeded(); bool recomputeCaretRect(); // returns true if caret rect moved void invalidateCaretRect(); void paintCaret(GraphicsContext*, int tx, int ty, const IntRect& clipRect); @@ -124,6 +127,9 @@ public: bool isFocusedAndActive() const; void pageActivationChanged(); + // Painting. + void updateAppearance(); + #ifndef NDEBUG void formatForDebugger(char* buffer, unsigned length) const; void showTreeForThis() const; @@ -148,44 +154,42 @@ private: int xPosForVerticalArrowNavigation(EPositionType); -#if PLATFORM(MAC) || PLATFORM(GTK) void notifyAccessibilityForSelectionChange(); -#else - void notifyAccessibilityForSelectionChange() {}; -#endif void focusedOrActiveStateChanged(); bool caretRendersInsideNode(Node*) const; IntRect absoluteBoundsForLocalRect(const IntRect&) const; + void caretBlinkTimerFired(Timer<SelectionController>*); + Frame* m_frame; + int m_xPosForVerticalArrowNavigation; - VisibleSelection m_sel; + VisibleSelection m_selection; + + Timer<SelectionController> m_caretBlinkTimer; - IntRect m_caretRect; // caret rect in coords local to the renderer responsible for painting the caret - IntRect m_absCaretBounds; // absolute bounding rect for the caret + IntRect m_caretRect; // caret rect in coords local to the renderer responsible for painting the caret + IntRect m_absCaretBounds; // absolute bounding rect for the caret IntRect m_absoluteCaretRepaintBounds; - bool m_needsLayout : 1; // true if the caret and expectedVisible rectangles need to be calculated - bool m_absCaretBoundsDirty: 1; - bool m_lastChangeWasHorizontalExtension : 1; - bool m_isDragCaretController : 1; - bool m_isCaretBlinkingSuspended : 1; - bool m_focused : 1; - + bool m_needsLayout; // true if m_caretRect and m_absCaretBounds need to be calculated + bool m_absCaretBoundsDirty; + bool m_lastChangeWasHorizontalExtension; + bool m_isDragCaretController; + bool m_isCaretBlinkingSuspended; + bool m_focused; + bool m_caretVisible; + bool m_caretPaint; }; -inline bool operator==(const SelectionController& a, const SelectionController& b) +#if !(PLATFORM(MAC) || PLATFORM(GTK)) +inline void SelectionController::notifyAccessibilityForSelectionChange() { - return a.start() == b.start() && a.end() == b.end() && a.affinity() == b.affinity(); -} - -inline bool operator!=(const SelectionController& a, const SelectionController& b) -{ - return !(a == b); } +#endif } // namespace WebCore diff --git a/WebCore/editing/TextIterator.cpp b/WebCore/editing/TextIterator.cpp index df271b6..923f537 100644 --- a/WebCore/editing/TextIterator.cpp +++ b/WebCore/editing/TextIterator.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. * Copyright (C) 2005 Alexey Proskuryakov. * * Redistribution and use in source and binary forms, with or without @@ -73,11 +73,17 @@ public: #if USE(ICU_UNICODE) && !UCONFIG_NO_COLLATION private: + bool isBadMatch(const UChar*, size_t length) const; + String m_target; Vector<UChar> m_buffer; size_t m_overlap; bool m_atBreak; + bool m_targetRequiresKanaWorkaround; + Vector<UChar> m_normalizedTarget; + mutable Vector<UChar> m_normalizedMatch; + #else private: @@ -150,7 +156,7 @@ static inline Node* parentCrossingShadowBoundaries(Node* node) return node->shadowParentNode(); } -#ifndef NDEBUG +#if !ASSERT_DISABLED static unsigned depthCrossingShadowBoundaries(Node* node) { @@ -705,6 +711,28 @@ static bool shouldEmitExtraNewlineForNode(Node* node) return false; } +static int collapsedSpaceLength(RenderText* renderer, int textEnd) +{ + const UChar* characters = renderer->text()->characters(); + int length = renderer->text()->length(); + for (int i = textEnd; i < length; ++i) { + if (!renderer->style()->isCollapsibleWhiteSpace(characters[i])) + return i - textEnd; + } + + return length - textEnd; +} + +static int maxOffsetIncludingCollapsedSpaces(Node* node) +{ + int offset = caretMaxOffset(node); + + if (node->renderer() && node->renderer()->isText()) + offset += collapsedSpaceLength(toRenderText(node->renderer()), offset); + + return offset; +} + // Whether or not we should emit a character as we enter m_node (if it's a container) or as we hit it (if it's atomic). bool TextIterator::shouldRepresentNodeOffsetZero() { @@ -1029,7 +1057,9 @@ void SimplifiedBackwardsTextIterator::advance() m_node = next; if (m_node) pushFullyClippedState(m_fullyClippedStack, m_node); - m_offset = m_node ? caretMaxOffset(m_node) : 0; + // For the purpose of word boundary detection, + // we should iterate all visible text and trailing (collapsed) whitespaces. + m_offset = m_node ? maxOffsetIncludingCollapsedSpaces(m_node) : 0; m_handledNode = false; m_handledChildren = false; @@ -1465,9 +1495,212 @@ static inline void unlockSearcher() #endif } +// ICU's search ignores the distinction between small kana letters and ones +// that are not small, and also characters that differ only in the voicing +// marks when considering only primary collation strength diffrences. +// This is not helpful for end users, since these differences make words +// distinct, so for our purposes we need these to be considered. +// The Unicode folks do not think the collation algorithm should be +// changed. To work around this, we would like to tailor the ICU searcher, +// but we can't get that to work yet. So instead, we check for cases where +// these differences occur, and skip those matches. + +// We refer to the above technique as the "kana workaround". The next few +// functions are helper functinos for the kana workaround. + +static inline bool isKanaLetter(UChar character) +{ + // Hiragana letters. + if (character >= 0x3041 && character <= 0x3096) + return true; + + // Katakana letters. + if (character >= 0x30A1 && character <= 0x30FA) + return true; + if (character >= 0x31F0 && character <= 0x31FF) + return true; + + // Halfwidth katakana letters. + if (character >= 0xFF66 && character <= 0xFF9D && character != 0xFF70) + return true; + + return false; +} + +static inline bool isSmallKanaLetter(UChar character) +{ + ASSERT(isKanaLetter(character)); + + switch (character) { + case 0x3041: // HIRAGANA LETTER SMALL A + case 0x3043: // HIRAGANA LETTER SMALL I + case 0x3045: // HIRAGANA LETTER SMALL U + case 0x3047: // HIRAGANA LETTER SMALL E + case 0x3049: // HIRAGANA LETTER SMALL O + case 0x3063: // HIRAGANA LETTER SMALL TU + case 0x3083: // HIRAGANA LETTER SMALL YA + case 0x3085: // HIRAGANA LETTER SMALL YU + case 0x3087: // HIRAGANA LETTER SMALL YO + case 0x308E: // HIRAGANA LETTER SMALL WA + case 0x3095: // HIRAGANA LETTER SMALL KA + case 0x3096: // HIRAGANA LETTER SMALL KE + case 0x30A1: // KATAKANA LETTER SMALL A + case 0x30A3: // KATAKANA LETTER SMALL I + case 0x30A5: // KATAKANA LETTER SMALL U + case 0x30A7: // KATAKANA LETTER SMALL E + case 0x30A9: // KATAKANA LETTER SMALL O + case 0x30C3: // KATAKANA LETTER SMALL TU + case 0x30E3: // KATAKANA LETTER SMALL YA + case 0x30E5: // KATAKANA LETTER SMALL YU + case 0x30E7: // KATAKANA LETTER SMALL YO + case 0x30EE: // KATAKANA LETTER SMALL WA + case 0x30F5: // KATAKANA LETTER SMALL KA + case 0x30F6: // KATAKANA LETTER SMALL KE + case 0x31F0: // KATAKANA LETTER SMALL KU + case 0x31F1: // KATAKANA LETTER SMALL SI + case 0x31F2: // KATAKANA LETTER SMALL SU + case 0x31F3: // KATAKANA LETTER SMALL TO + case 0x31F4: // KATAKANA LETTER SMALL NU + case 0x31F5: // KATAKANA LETTER SMALL HA + case 0x31F6: // KATAKANA LETTER SMALL HI + case 0x31F7: // KATAKANA LETTER SMALL HU + case 0x31F8: // KATAKANA LETTER SMALL HE + case 0x31F9: // KATAKANA LETTER SMALL HO + case 0x31FA: // KATAKANA LETTER SMALL MU + case 0x31FB: // KATAKANA LETTER SMALL RA + case 0x31FC: // KATAKANA LETTER SMALL RI + case 0x31FD: // KATAKANA LETTER SMALL RU + case 0x31FE: // KATAKANA LETTER SMALL RE + case 0x31FF: // KATAKANA LETTER SMALL RO + case 0xFF67: // HALFWIDTH KATAKANA LETTER SMALL A + case 0xFF68: // HALFWIDTH KATAKANA LETTER SMALL I + case 0xFF69: // HALFWIDTH KATAKANA LETTER SMALL U + case 0xFF6A: // HALFWIDTH KATAKANA LETTER SMALL E + case 0xFF6B: // HALFWIDTH KATAKANA LETTER SMALL O + case 0xFF6C: // HALFWIDTH KATAKANA LETTER SMALL YA + case 0xFF6D: // HALFWIDTH KATAKANA LETTER SMALL YU + case 0xFF6E: // HALFWIDTH KATAKANA LETTER SMALL YO + case 0xFF6F: // HALFWIDTH KATAKANA LETTER SMALL TU + return true; + } + return false; +} + +enum VoicedSoundMarkType { NoVoicedSoundMark, VoicedSoundMark, SemiVoicedSoundMark }; + +static inline VoicedSoundMarkType composedVoicedSoundMark(UChar character) +{ + ASSERT(isKanaLetter(character)); + + switch (character) { + case 0x304C: // HIRAGANA LETTER GA + case 0x304E: // HIRAGANA LETTER GI + case 0x3050: // HIRAGANA LETTER GU + case 0x3052: // HIRAGANA LETTER GE + case 0x3054: // HIRAGANA LETTER GO + case 0x3056: // HIRAGANA LETTER ZA + case 0x3058: // HIRAGANA LETTER ZI + case 0x305A: // HIRAGANA LETTER ZU + case 0x305C: // HIRAGANA LETTER ZE + case 0x305E: // HIRAGANA LETTER ZO + case 0x3060: // HIRAGANA LETTER DA + case 0x3062: // HIRAGANA LETTER DI + case 0x3065: // HIRAGANA LETTER DU + case 0x3067: // HIRAGANA LETTER DE + case 0x3069: // HIRAGANA LETTER DO + case 0x3070: // HIRAGANA LETTER BA + case 0x3073: // HIRAGANA LETTER BI + case 0x3076: // HIRAGANA LETTER BU + case 0x3079: // HIRAGANA LETTER BE + case 0x307C: // HIRAGANA LETTER BO + case 0x3094: // HIRAGANA LETTER VU + case 0x30AC: // KATAKANA LETTER GA + case 0x30AE: // KATAKANA LETTER GI + case 0x30B0: // KATAKANA LETTER GU + case 0x30B2: // KATAKANA LETTER GE + case 0x30B4: // KATAKANA LETTER GO + case 0x30B6: // KATAKANA LETTER ZA + case 0x30B8: // KATAKANA LETTER ZI + case 0x30BA: // KATAKANA LETTER ZU + case 0x30BC: // KATAKANA LETTER ZE + case 0x30BE: // KATAKANA LETTER ZO + case 0x30C0: // KATAKANA LETTER DA + case 0x30C2: // KATAKANA LETTER DI + case 0x30C5: // KATAKANA LETTER DU + case 0x30C7: // KATAKANA LETTER DE + case 0x30C9: // KATAKANA LETTER DO + case 0x30D0: // KATAKANA LETTER BA + case 0x30D3: // KATAKANA LETTER BI + case 0x30D6: // KATAKANA LETTER BU + case 0x30D9: // KATAKANA LETTER BE + case 0x30DC: // KATAKANA LETTER BO + case 0x30F4: // KATAKANA LETTER VU + case 0x30F7: // KATAKANA LETTER VA + case 0x30F8: // KATAKANA LETTER VI + case 0x30F9: // KATAKANA LETTER VE + case 0x30FA: // KATAKANA LETTER VO + return VoicedSoundMark; + case 0x3071: // HIRAGANA LETTER PA + case 0x3074: // HIRAGANA LETTER PI + case 0x3077: // HIRAGANA LETTER PU + case 0x307A: // HIRAGANA LETTER PE + case 0x307D: // HIRAGANA LETTER PO + case 0x30D1: // KATAKANA LETTER PA + case 0x30D4: // KATAKANA LETTER PI + case 0x30D7: // KATAKANA LETTER PU + case 0x30DA: // KATAKANA LETTER PE + case 0x30DD: // KATAKANA LETTER PO + return SemiVoicedSoundMark; + } + return NoVoicedSoundMark; +} + +static inline bool isCombiningVoicedSoundMark(UChar character) +{ + switch (character) { + case 0x3099: // COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK + case 0x309A: // COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK + return true; + } + return false; +} + +static inline bool containsKanaLetters(const String& pattern) +{ + const UChar* characters = pattern.characters(); + unsigned length = pattern.length(); + for (unsigned i = 0; i < length; ++i) { + if (isKanaLetter(characters[i])) + return true; + } + return false; +} + +static void normalizeCharacters(const UChar* characters, unsigned length, Vector<UChar>& buffer) +{ + ASSERT(length); + + buffer.resize(length); + + UErrorCode status = U_ZERO_ERROR; + size_t bufferSize = unorm_normalize(characters, length, UNORM_NFC, 0, buffer.data(), length, &status); + ASSERT(status == U_ZERO_ERROR || status == U_STRING_NOT_TERMINATED_WARNING || status == U_BUFFER_OVERFLOW_ERROR); + ASSERT(bufferSize); + + buffer.resize(bufferSize); + + if (status == U_ZERO_ERROR || status == U_STRING_NOT_TERMINATED_WARNING) + return; + + status = U_ZERO_ERROR; + unorm_normalize(characters, length, UNORM_NFC, 0, buffer.data(), bufferSize, &status); + ASSERT(status == U_STRING_NOT_TERMINATED_WARNING); +} + inline SearchBuffer::SearchBuffer(const String& target, bool isCaseSensitive) : m_target(target) , m_atBreak(true) + , m_targetRequiresKanaWorkaround(containsKanaLetters(m_target)) { ASSERT(!m_target.isEmpty()); @@ -1497,6 +1730,10 @@ inline SearchBuffer::SearchBuffer(const String& target, bool isCaseSensitive) UErrorCode status = U_ZERO_ERROR; usearch_setPattern(searcher, m_target.characters(), targetLength, &status); ASSERT(status == U_ZERO_ERROR); + + // The kana workaround requires a normalized copy of the target string. + if (m_targetRequiresKanaWorkaround) + normalizeCharacters(m_target.characters(), m_target.length(), m_normalizedTarget); } inline SearchBuffer::~SearchBuffer() @@ -1534,6 +1771,66 @@ inline void SearchBuffer::reachedBreak() m_atBreak = true; } +inline bool SearchBuffer::isBadMatch(const UChar* match, size_t matchLength) const +{ + // This function implements the kana workaround. If usearch treats + // it as a match, but we do not want to, then it's a "bad match". + if (!m_targetRequiresKanaWorkaround) + return false; + + // Normalize into a match buffer. We reuse a single buffer rather than + // creating a new one each time. + normalizeCharacters(match, matchLength, m_normalizedMatch); + + const UChar* a = m_normalizedTarget.begin(); + const UChar* aEnd = m_normalizedTarget.end(); + + const UChar* b = m_normalizedMatch.begin(); + const UChar* bEnd = m_normalizedMatch.end(); + + while (true) { + // Skip runs of non-kana-letter characters. This is necessary so we can + // correctly handle strings where the target and match have different-length + // runs of characters that match, while still double checking the correctness + // of matches of kana letters with other kana letters. + while (a != aEnd && !isKanaLetter(*a)) + ++a; + while (b != bEnd && !isKanaLetter(*b)) + ++b; + + // If we reached the end of either the target or the match, we should have + // reached the end of both; both should have the same number of kana letters. + if (a == aEnd || b == bEnd) { + ASSERT(a == aEnd); + ASSERT(b == bEnd); + return false; + } + + // Check for differences in the kana letter character itself. + if (isSmallKanaLetter(*a) != isSmallKanaLetter(*b)) + return true; + if (composedVoicedSoundMark(*a) != composedVoicedSoundMark(*b)) + return true; + ++a; + ++b; + + // Check for differences in combining voiced sound marks found after the letter. + while (1) { + if (!(a != aEnd && isCombiningVoicedSoundMark(*a))) { + if (b != bEnd && isCombiningVoicedSoundMark(*b)) + return true; + break; + } + if (!(b != bEnd && isCombiningVoicedSoundMark(*b))) + return true; + if (*a != *b) + return true; + ++a; + ++b; + } + } +} + inline size_t SearchBuffer::search(size_t& start) { size_t size = m_buffer.size(); @@ -1553,6 +1850,8 @@ inline size_t SearchBuffer::search(size_t& start) int matchStart = usearch_first(searcher, &status); ASSERT(status == U_ZERO_ERROR); + +nextMatch: if (!(matchStart >= 0 && static_cast<size_t>(matchStart) < size)) { ASSERT(matchStart == USEARCH_DONE); return 0; @@ -1567,12 +1866,22 @@ inline size_t SearchBuffer::search(size_t& start) return 0; } + size_t matchedLength = usearch_getMatchedLength(searcher); + ASSERT(matchStart + matchedLength <= size); + + // If this match is "bad", move on to the next match. + if (isBadMatch(m_buffer.data() + matchStart, matchedLength)) { + matchStart = usearch_next(searcher, &status); + ASSERT(status == U_ZERO_ERROR); + goto nextMatch; + } + size_t newSize = size - (matchStart + 1); memmove(m_buffer.data(), m_buffer.data() + matchStart + 1, newSize * sizeof(UChar)); m_buffer.shrink(newSize); start = size - matchStart; - return usearch_getMatchedLength(searcher); + return matchedLength; } #else // !ICU_UNICODE diff --git a/WebCore/editing/TypingCommand.cpp b/WebCore/editing/TypingCommand.cpp index 2b0f61e..1c9db90 100644 --- a/WebCore/editing/TypingCommand.cpp +++ b/WebCore/editing/TypingCommand.cpp @@ -450,6 +450,10 @@ void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing) } VisiblePosition visibleStart(endingSelection().visibleStart()); + // If we have a caret selection on an empty cell, we have nothing to do. + if (isEmptyTableCell(visibleStart.deepEquivalent().node())) + return; + // If the caret is at the start of a paragraph after a table, move content into the last table cell. if (isStartOfParagraph(visibleStart) && isFirstPositionAfterTable(visibleStart.previous(true))) { // Unless the caret is just before a table. We don't want to move a table into the last table cell. diff --git a/WebCore/editing/VisibleSelection.cpp b/WebCore/editing/VisibleSelection.cpp index 68d5a3e..baef2b5 100644 --- a/WebCore/editing/VisibleSelection.cpp +++ b/WebCore/editing/VisibleSelection.cpp @@ -301,7 +301,7 @@ void VisibleSelection::setStartAndEndFromBaseAndExtentRespectingGranularity() VisiblePosition wordEnd(endOfWord(originalEnd, side)); VisiblePosition end(wordEnd); - if (isEndOfParagraph(originalEnd)) { + if (isEndOfParagraph(originalEnd) && !isEmptyTableCell(m_start.node())) { // Select the paragraph break (the space from the end of a paragraph to the start of // the next one) to match TextEdit. end = wordEnd.next(); diff --git a/WebCore/editing/VisibleSelection.h b/WebCore/editing/VisibleSelection.h index e346b27..bbcecf2 100644 --- a/WebCore/editing/VisibleSelection.h +++ b/WebCore/editing/VisibleSelection.h @@ -114,7 +114,11 @@ private: void adjustSelectionToAvoidCrossingEditingBoundaries(); void updateSelectionType(); - // FIXME: These should all be VisiblePositions + // We need to store these as Positions because VisibleSelection is + // used to store values in editing commands for use when + // undoing the command. We need to be able to create a selection that, while currently + // invalid, will be valid once the changes are undone. + Position m_base; // Where the first click happened Position m_extent; // Where the end click happened Position m_start; // Leftmost position when expanded to respect granularity diff --git a/WebCore/editing/gtk/SelectionControllerGtk.cpp b/WebCore/editing/gtk/SelectionControllerGtk.cpp index f3bd4bc..6a3258a 100644 --- a/WebCore/editing/gtk/SelectionControllerGtk.cpp +++ b/WebCore/editing/gtk/SelectionControllerGtk.cpp @@ -20,6 +20,7 @@ #include "config.h" #include "SelectionController.h" +#include "AccessibilityObjectWrapperAtk.h" #include "AXObjectCache.h" #include "Frame.h" @@ -29,14 +30,16 @@ namespace WebCore { void SelectionController::notifyAccessibilityForSelectionChange() { - if (AXObjectCache::accessibilityEnabled() && m_sel.start().isNotNull() && m_sel.end().isNotNull()) { - RenderObject* focusedNode = m_sel.end().node()->renderer(); + if (AXObjectCache::accessibilityEnabled() && m_selection.start().isNotNull() && m_selection.end().isNotNull()) { + RenderObject* focusedNode = m_selection.end().node()->renderer(); AccessibilityObject* accessibilityObject = m_frame->document()->axObjectCache()->getOrCreate(focusedNode); - AtkObject* wrapper = accessibilityObject->wrapper(); + int offset; + // Always report the events w.r.t. the non-linked unignored parent. (i.e. ignoreLinks == true) + AccessibilityObject* object = objectAndOffsetUnignored(accessibilityObject, offset, true); + AtkObject* wrapper = object->wrapper(); if (ATK_IS_TEXT(wrapper)) { - g_signal_emit_by_name(wrapper, "text-caret-moved", m_sel.end().computeOffsetInContainerNode()); - - if (m_sel.isRange()) + g_signal_emit_by_name(wrapper, "text-caret-moved", offset); + if (m_selection.isRange()) g_signal_emit_by_name(wrapper, "text-selection-changed"); } } diff --git a/WebCore/editing/htmlediting.cpp b/WebCore/editing/htmlediting.cpp index 8b1c98d..b58dff3 100644 --- a/WebCore/editing/htmlediting.cpp +++ b/WebCore/editing/htmlediting.cpp @@ -474,20 +474,25 @@ bool validBlockTag(const AtomicString& blockTag) DEFINE_STATIC_LOCAL(HashSet<AtomicString>, blockTags, ()); if (blockTags.isEmpty()) { blockTags.add(addressTag.localName()); + blockTags.add(articleTag.localName()); + blockTags.add(asideTag.localName()); blockTags.add(blockquoteTag.localName()); blockTags.add(ddTag.localName()); blockTags.add(divTag.localName()); blockTags.add(dlTag.localName()); blockTags.add(dtTag.localName()); + blockTags.add(footerTag.localName()); blockTags.add(h1Tag.localName()); blockTags.add(h2Tag.localName()); blockTags.add(h3Tag.localName()); blockTags.add(h4Tag.localName()); blockTags.add(h5Tag.localName()); blockTags.add(h6Tag.localName()); + blockTags.add(headerTag.localName()); blockTags.add(navTag.localName()); blockTags.add(pTag.localName()); blockTags.add(preTag.localName()); + blockTags.add(sectionTag.localName()); } return blockTags.contains(blockTag); } @@ -851,6 +856,11 @@ bool isTableCell(const Node* node) return r->isTableCell(); } +bool isEmptyTableCell(const Node* node) +{ + return node && node->renderer() && (node->renderer()->isTableCell() || (node->renderer()->isBR() && node->parentNode()->renderer() && node->parentNode()->renderer()->isTableCell())); +} + PassRefPtr<HTMLElement> createDefaultParagraphElement(Document* document) { return new HTMLDivElement(divTag, document); @@ -973,7 +983,7 @@ unsigned numEnclosingMailBlockquotes(const Position& p) bool isMailBlockquote(const Node *node) { - if (!node || (!node->isElementNode() && !node->hasTagName(blockquoteTag))) + if (!node || !node->hasTagName(blockquoteTag)) return false; return static_cast<const Element *>(node)->getAttribute("type") == "cite"; diff --git a/WebCore/editing/htmlediting.h b/WebCore/editing/htmlediting.h index 43048e0..c5a44ac 100644 --- a/WebCore/editing/htmlediting.h +++ b/WebCore/editing/htmlediting.h @@ -87,6 +87,7 @@ bool isTabSpanTextNode(const Node*); bool isMailBlockquote(const Node*); bool isTableElement(Node*); bool isTableCell(const Node*); +bool isEmptyTableCell(const Node*); bool isTableStructureNode(const Node*); bool isListElement(Node*); bool isNodeRendered(const Node*); diff --git a/WebCore/editing/mac/SelectionControllerMac.mm b/WebCore/editing/mac/SelectionControllerMac.mm index 47fb434..730eb60 100644 --- a/WebCore/editing/mac/SelectionControllerMac.mm +++ b/WebCore/editing/mac/SelectionControllerMac.mm @@ -37,11 +37,11 @@ void SelectionController::notifyAccessibilityForSelectionChange() { Document* document = m_frame->document(); - if (AXObjectCache::accessibilityEnabled() && m_sel.start().isNotNull() && m_sel.end().isNotNull()) - document->axObjectCache()->postNotification(m_sel.start().node()->renderer(), AXObjectCache::AXSelectedTextChanged, false); + if (AXObjectCache::accessibilityEnabled() && m_selection.start().isNotNull() && m_selection.end().isNotNull()) + document->axObjectCache()->postNotification(m_selection.start().node()->renderer(), AXObjectCache::AXSelectedTextChanged, false); // if zoom feature is enabled, insertion point changes should update the zoom - if (!UAZoomEnabled() || !m_sel.isCaret()) + if (!UAZoomEnabled() || !m_selection.isCaret()) return; RenderView* renderView = document->renderView(); diff --git a/WebCore/editing/markup.cpp b/WebCore/editing/markup.cpp index 47714e7..dc6cbc2 100644 --- a/WebCore/editing/markup.cpp +++ b/WebCore/editing/markup.cpp @@ -55,6 +55,7 @@ #include "Range.h" #include "VisibleSelection.h" #include "TextIterator.h" +#include "XMLNSNames.h" #include "htmlediting.h" #include "visible_units.h" #include <wtf/StdLibExtras.h> @@ -314,15 +315,15 @@ static bool shouldAddNamespaceElem(const Element* elem) static bool shouldAddNamespaceAttr(const Attribute* attr, HashMap<AtomicStringImpl*, AtomicStringImpl*>& namespaces) { + namespaces.checkConsistency(); + // Don't add namespace attributes twice - DEFINE_STATIC_LOCAL(const AtomicString, xmlnsURI, ("http://www.w3.org/2000/xmlns/")); - DEFINE_STATIC_LOCAL(const QualifiedName, xmlnsAttr, (nullAtom, "xmlns", xmlnsURI)); - if (attr->name() == xmlnsAttr) { + if (attr->name() == XMLNSNames::xmlnsAttr) { namespaces.set(emptyAtom.impl(), attr->value().impl()); return false; } - QualifiedName xmlnsPrefixAttr("xmlns", attr->localName(), xmlnsURI); + QualifiedName xmlnsPrefixAttr(xmlnsAtom, attr->localName(), XMLNSNames::xmlnsNamespaceURI); if (attr->name() == xmlnsPrefixAttr) { namespaces.set(attr->localName().impl(), attr->value().impl()); return false; @@ -333,6 +334,7 @@ static bool shouldAddNamespaceAttr(const Attribute* attr, HashMap<AtomicStringIm static void appendNamespace(Vector<UChar>& result, const AtomicString& prefix, const AtomicString& ns, HashMap<AtomicStringImpl*, AtomicStringImpl*>& namespaces) { + namespaces.checkConsistency(); if (ns.isEmpty()) return; @@ -341,9 +343,8 @@ static void appendNamespace(Vector<UChar>& result, const AtomicString& prefix, c AtomicStringImpl* foundNS = namespaces.get(pre); if (foundNS != ns.impl()) { namespaces.set(pre, ns.impl()); - DEFINE_STATIC_LOCAL(const String, xmlns, ("xmlns")); result.append(' '); - append(result, xmlns); + append(result, xmlnsAtom.string()); if (!prefix.isEmpty()) { result.append(':'); append(result, prefix); @@ -394,6 +395,9 @@ enum RangeFullySelectsNode { DoesFullySelectNode, DoesNotFullySelectNode }; static void appendStartMarkup(Vector<UChar>& result, const Node* node, const Range* range, EAnnotateForInterchange annotate, bool convertBlocksToInlines = false, HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0, RangeFullySelectsNode rangeFullySelectsNode = DoesFullySelectNode) { + if (namespaces) + namespaces->checkConsistency(); + bool documentIsHTML = node->document()->isHTMLDocument(); switch (node->nodeType()) { case Node::TEXT_NODE: { @@ -1052,13 +1056,13 @@ String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterc return joinMarkups(preMarkups, markups); } -PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document* document, const String& markup, const String& baseURL) +PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document* document, const String& markup, const String& baseURL, FragmentScriptingPermission scriptingPermission) { ASSERT(document->documentElement()->isHTMLElement()); // FIXME: What if the document element is not an HTML element? HTMLElement *element = static_cast<HTMLElement*>(document->documentElement()); - RefPtr<DocumentFragment> fragment = element->createContextualFragment(markup); + RefPtr<DocumentFragment> fragment = element->createContextualFragment(markup, scriptingPermission); if (fragment && !baseURL.isEmpty() && baseURL != blankURL() && baseURL != document->baseURL()) completeURLs(fragment.get(), baseURL); @@ -1129,6 +1133,17 @@ static void fillContainerFromString(ContainerNode* paragraph, const String& stri } } +bool isPlainTextMarkup(Node *node) +{ + if (!node->isElementNode() || !node->hasTagName(divTag) || static_cast<Element*>(node)->attributes()->length()) + return false; + + if (node->childNodeCount() == 1 && (node->firstChild()->isTextNode() || (node->firstChild()->firstChild()))) + return true; + + return (node->childNodeCount() == 2 && isTabSpanTextNode(node->firstChild()->firstChild()) && node->firstChild()->nextSibling()->isTextNode()); +} + PassRefPtr<DocumentFragment> createFragmentFromText(Range* context, const String& text) { if (!context) diff --git a/WebCore/editing/markup.h b/WebCore/editing/markup.h index 6b7333c..5ace04a 100644 --- a/WebCore/editing/markup.h +++ b/WebCore/editing/markup.h @@ -27,6 +27,7 @@ #define markup_h #include "HTMLInterchange.h" +#include "MappedAttributeEntry.h" #include <wtf/Forward.h> #include <wtf/Vector.h> @@ -41,9 +42,11 @@ namespace WebCore { enum EChildrenOnly { IncludeNode, ChildrenOnly }; PassRefPtr<DocumentFragment> createFragmentFromText(Range* context, const String& text); - PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document*, const String& markup, const String& baseURL); + PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document*, const String& markup, const String& baseURL, FragmentScriptingPermission = FragmentScriptingAllowed); PassRefPtr<DocumentFragment> createFragmentFromNodes(Document*, const Vector<Node*>&); + bool isPlainTextMarkup(Node *node); + String createMarkup(const Range*, Vector<Node*>* = 0, EAnnotateForInterchange = DoNotAnnotateForInterchange, bool convertBlocksToInlines = false); String createMarkup(const Node*, EChildrenOnly = IncludeNode, Vector<Node*>* = 0); |