summaryrefslogtreecommitdiffstats
path: root/WebCore/editing
diff options
context:
space:
mode:
authorSteve Block <steveblock@google.com>2010-02-02 14:57:50 +0000
committerSteve Block <steveblock@google.com>2010-02-04 15:06:55 +0000
commitd0825bca7fe65beaee391d30da42e937db621564 (patch)
tree7461c49eb5844ffd1f35d1ba2c8b7584c1620823 /WebCore/editing
parent3db770bd97c5a59b6c7574ca80a39e5a51c1defd (diff)
downloadexternal_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.cpp12
-rw-r--r--WebCore/editing/CompositeEditCommand.cpp12
-rw-r--r--WebCore/editing/DeleteButtonController.cpp8
-rw-r--r--WebCore/editing/DeleteSelectionCommand.cpp11
-rw-r--r--WebCore/editing/Editor.cpp9
-rw-r--r--WebCore/editing/EditorCommand.cpp5
-rw-r--r--WebCore/editing/IndentOutdentCommand.cpp7
-rw-r--r--WebCore/editing/ReplaceSelectionCommand.cpp11
-rw-r--r--WebCore/editing/SelectionController.cpp313
-rw-r--r--WebCore/editing/SelectionController.h82
-rw-r--r--WebCore/editing/TextIterator.cpp317
-rw-r--r--WebCore/editing/TypingCommand.cpp4
-rw-r--r--WebCore/editing/VisibleSelection.cpp2
-rw-r--r--WebCore/editing/VisibleSelection.h6
-rw-r--r--WebCore/editing/gtk/SelectionControllerGtk.cpp15
-rw-r--r--WebCore/editing/htmlediting.cpp12
-rw-r--r--WebCore/editing/htmlediting.h1
-rw-r--r--WebCore/editing/mac/SelectionControllerMac.mm6
-rw-r--r--WebCore/editing/markup.cpp31
-rw-r--r--WebCore/editing/markup.h5
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);