Commit 73ecea19 authored by ap@webkit.org's avatar ap@webkit.org

Reviewed by Darin Adler.

        <rdar://problem/6438271> Improve backspace handling of accented characters

        Test: platform/mac/editing/deleting/backward-delete.html

        This change makes WebKit behave like AppKit on the Mac. Other platforms are not affected,
        because the current behavior matches their standards (tested on Windows, assumed elsewhere).

        * dom/Position.cpp:
        (WebCore::Position::previous):
        (WebCore::Position::next):
        (WebCore::Position::uncheckedPreviousOffsetForBackwardDeletion):
        * dom/Position.h:
        Added a new option for previous(), used to move to a next position for backward deletion.
        Renamed EUsingComposedCharacters to PositionMoveType.

        * editing/TypingCommand.cpp: (WebCore::TypingCommand::deleteKeyPressed):
        Use previous(BackwardDeletion) to find a proper range to delete. Also, simplified a check
        surrounding this code a little.

        * editing/htmlediting.cpp:
        (WebCore::nextVisuallyDistinctCandidate):
        (WebCore::previousVisuallyDistinctCandidate):
        Adjusted for renamed enum values.

        * rendering/RenderObject.cpp:
        * rendering/RenderObject.h:
        * rendering/RenderText.h:
        Added previousOffsetForBackwardDeletion().

        * rendering/RenderText.cpp: (WebCore::RenderText::previousOffsetForBackwardDeletion):
        On PLATFORM(MAC), use an algorithm that matches the one AppKit has for backward deletion.



git-svn-id: http://svn.webkit.org/repository/webkit/trunk@40710 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 5396c974
2009-02-05 Alexey Proskuryakov <ap@webkit.org>
Reviewed by Darin Adler.
<rdar://problem/6438271> Improve backspace handling of accented characters
* platform/mac/editing/deleting/backward-delete-expected.txt: Added.
* platform/mac/editing/deleting/backward-delete.html: Added.
2009-02-05 Nikolas Zimmermann <nikolas.zimmermann@torchmobile.com>
Rubber-stamped by George Staikos.
......
Test that backward delete behavior matches AppKit.
Test:
Expected
Test:
Expected
Test:
Expected ว
Test:
Expected
Test:
Expected
Test:
Expected
Test:
ش
Expected ش
Test:
א
Expected א
Test:
Expected
Test:
Expected
Test:
Expected
Test:
Expected
<body>
<p>Test that backward delete behavior matches AppKit.</p>
<p id=status></p>
<table>
<tr><td>Test:</td><td><div contenteditable></div></td><td>Expected</td><td></td></tr>
<tr><td>Test:</td><td><div contenteditable>а́</div></td><td>Expected</td><td></td></tr>
<tr><td>Test:</td><td><div contenteditable>วั</div></td><td>Expected</td><td></td></tr>
<tr><td>Test:</td><td><div contenteditable>ế</div></td><td>Expected</td><td></td></tr>
<tr><td>Test:</td><td><div contenteditable>q̣̇</div></td><td>Expected</td><td></td></tr>
<tr><td>Test:</td><td><div contenteditable></div></td><td>Expected</td><td></td></tr>
<tr><td>Test:</td><td><div contenteditable>شْ</div></td><td>Expected</td><td>ش</td></tr>
<tr><td>Test:</td><td><div contenteditable>אֱ</div></td><td>Expected</td><td>א</td></tr>
<tr><td>Test:</td><td><div contenteditable>각</div></td><td>Expected</td><td></td></tr>
<tr><td>Test:</td><td><div contenteditable>ダ</div></td><td>Expected</td><td></td></tr>
<tr><td>Test:</td><td><div contenteditable>ダ</div></td><td>Expected</td><td></td></tr>
<tr><td>Test:</td><td><div contenteditable>𐐀</div></td><td>Expected</td><td></td></tr>
</table>
<script>
if (window.layoutTestController) {
layoutTestController.dumpAsText();
layoutTestController.waitUntilDone();
}
function test()
{
var node = document.evaluate("//*[@contenteditable]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (!node) {
if (window.layoutTestController)
layoutTestController.notifyDone();
else
document.getElementById("status").innerHTML = "DONE"
return;
}
node.focus();
window.getSelection().modify("move", "forward", "word");
node.onkeydown = function() {
setTimeout(function() { node.removeAttribute('contenteditable'); test(); }, 0);
}
if (window.eventSender)
eventSender.keyDown(String.fromCharCode(0x0008), null);
}
test();
if (!window.eventSender)
document.getElementById("status").innerHTML = "Please press Backspace once for each editable field."
</script>
</body>
2009-02-05 Alexey Proskuryakov <ap@webkit.org>
Reviewed by Darin Adler.
<rdar://problem/6438271> Improve backspace handling of accented characters
Test: platform/mac/editing/deleting/backward-delete.html
This change makes WebKit behave like AppKit on the Mac. Other platforms are not affected,
because the current behavior matches their standards (tested on Windows, assumed elsewhere).
* dom/Position.cpp:
(WebCore::Position::previous):
(WebCore::Position::next):
(WebCore::Position::uncheckedPreviousOffsetForBackwardDeletion):
* dom/Position.h:
Added a new option for previous(), used to move to a next position for backward deletion.
Renamed EUsingComposedCharacters to PositionMoveType.
* editing/TypingCommand.cpp: (WebCore::TypingCommand::deleteKeyPressed):
Use previous(BackwardDeletion) to find a proper range to delete. Also, simplified a check
surrounding this code a little.
* editing/htmlediting.cpp:
(WebCore::nextVisuallyDistinctCandidate):
(WebCore::previousVisuallyDistinctCandidate):
Adjusted for renamed enum values.
* rendering/RenderObject.cpp:
* rendering/RenderObject.h:
* rendering/RenderText.h:
Added previousOffsetForBackwardDeletion().
* rendering/RenderText.cpp: (WebCore::RenderText::previousOffsetForBackwardDeletion):
On PLATFORM(MAC), use an algorithm that matches the one AppKit has for backward deletion.
2009-02-05 Adam Roben <aroben@apple.com>
Fix a crash in RenderWidget::destroy when navigating away from a page
......@@ -97,9 +97,9 @@ PassRefPtr<CSSComputedStyleDeclaration> Position::computedStyle() const
return WebCore::computedStyle(elem);
}
Position Position::previous(EUsingComposedCharacters usingComposedCharacters) const
Position Position::previous(PositionMoveType moveType) const
{
Node *n = node();
Node* n = node();
if (!n)
return *this;
......@@ -108,28 +108,37 @@ Position Position::previous(EUsingComposedCharacters usingComposedCharacters) co
ASSERT(o >= 0);
if (o > 0) {
Node *child = n->childNode(o - 1);
if (child) {
Node* child = n->childNode(o - 1);
if (child)
return Position(child, maxDeepOffset(child));
}
// There are two reasons child might be 0:
// 1) The node is node like a text node that is not an element, and therefore has no children.
// Going backward one character at a time is correct.
// 2) The old offset was a bogus offset like (<br>, 1), and there is no child.
// Going from 1 to 0 is correct.
return Position(n, usingComposedCharacters ? uncheckedPreviousOffset(n, o) : o - 1);
switch (moveType) {
case CodePoint:
return Position(n, o - 1);
case Character:
return Position(n, uncheckedPreviousOffset(n, o));
case BackwardDeletion:
return Position(n, uncheckedPreviousOffsetForBackwardDeletion(n, o));
}
}
Node *parent = n->parentNode();
Node* parent = n->parentNode();
if (!parent)
return *this;
return Position(parent, n->nodeIndex());
}
Position Position::next(EUsingComposedCharacters usingComposedCharacters) const
Position Position::next(PositionMoveType moveType) const
{
Node *n = node();
ASSERT(moveType != BackwardDeletion);
Node* n = node();
if (!n)
return *this;
......@@ -147,10 +156,10 @@ Position Position::next(EUsingComposedCharacters usingComposedCharacters) const
// Going forward one character at a time is correct.
// 2) The new offset is a bogus offset like (<br>, 1), and there is no child.
// Going from 0 to 1 is correct.
return Position(n, usingComposedCharacters ? uncheckedNextOffset(n, o) : o + 1);
return Position(n, (moveType == Character) ? uncheckedNextOffset(n, o) : o + 1);
}
Node *parent = n->parentNode();
Node* parent = n->parentNode();
if (!parent)
return *this;
......@@ -162,6 +171,11 @@ int Position::uncheckedPreviousOffset(const Node* n, int current)
return n->renderer() ? n->renderer()->previousOffset(current) : current - 1;
}
int Position::uncheckedPreviousOffsetForBackwardDeletion(const Node* n, int current)
{
return n->renderer() ? n->renderer()->previousOffsetForBackwardDeletion(current) : current - 1;
}
int Position::uncheckedNextOffset(const Node* n, int current)
{
return n->renderer() ? n->renderer()->nextOffset(current) : current + 1;
......
......@@ -40,7 +40,11 @@ class Node;
class Range;
class RenderObject;
enum EUsingComposedCharacters { NotUsingComposedCharacters = false, UsingComposedCharacters = true };
enum PositionMoveType {
CodePoint, // Move by a single code point.
Character, // Move to the next Unicode character break.
BackwardDeletion // Subject to platform conventions.
};
// FIXME: Reduce the number of operations we have on a Position.
// This should be more like a humble struct, without so many different
......@@ -69,9 +73,10 @@ public:
// Move up or down the DOM by one position.
// Offsets are computed using render text for nodes that have renderers - but note that even when
// using composed characters, the result may be inside a single user-visible character if a ligature is formed.
Position previous(EUsingComposedCharacters usingComposedCharacters=NotUsingComposedCharacters) const;
Position next(EUsingComposedCharacters usingComposedCharacters=NotUsingComposedCharacters) const;
Position previous(PositionMoveType = CodePoint) const;
Position next(PositionMoveType = CodePoint) const;
static int uncheckedPreviousOffset(const Node*, int current);
static int uncheckedPreviousOffsetForBackwardDeletion(const Node*, int current);
static int uncheckedNextOffset(const Node*, int current);
bool atStart() const;
......
......@@ -417,15 +417,9 @@ void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing)
selectionToDelete = selection.selection();
if (granularity == CharacterGranularity && selectionToDelete.end().offset() - selectionToDelete.start().offset() > 1) {
// When we delete a ligature consisting of multiple Unicode characters with a backspace key,
// we should not delete the ligature but delete only its last characeter. To check we are deleting
// a ligature, we retrieve the previous position of the caret and count the number of
// characters to be deleted.
// To prevent from calculating the previous position every time when pressing a backspace key,
// we retrieve the previous position only when the given selection consists of two or more characters.
if (selectionToDelete.end().offset() - selectionToDelete.end().previous(UsingComposedCharacters).offset() > 1)
selectionToDelete.setWithoutValidation(selectionToDelete.end(), selectionToDelete.end().previous(NotUsingComposedCharacters));
if (granularity == CharacterGranularity && selectionToDelete.end().node() == selectionToDelete.start().node() && selectionToDelete.end().offset() - selectionToDelete.start().offset() > 1) {
// If there are multiple Unicode code points to be deleted, adjust the range to match platform conventions.
selectionToDelete.setWithoutValidation(selectionToDelete.end(), selectionToDelete.end().previous(BackwardDeletion));
}
if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start())
......
......@@ -222,7 +222,7 @@ Position nextVisuallyDistinctCandidate(const Position& position)
Position p = position;
Position downstreamStart = p.downstream();
while (!p.atEnd()) {
p = p.next(UsingComposedCharacters);
p = p.next(Character);
if (p.isCandidate() && p.downstream() != downstreamStart)
return p;
}
......@@ -245,7 +245,7 @@ Position previousVisuallyDistinctCandidate(const Position& position)
Position p = position;
Position downstreamStart = p.downstream();
while (!p.atStart()) {
p = p.previous(UsingComposedCharacters);
p = p.previous(Character);
if (p.isCandidate() && p.downstream() != downstreamStart)
return p;
}
......
......@@ -2744,6 +2744,11 @@ int RenderObject::previousOffset(int current) const
return current - 1;
}
int RenderObject::previousOffsetForBackwardDeletion(int current) const
{
return current - 1;
}
int RenderObject::nextOffset(int current) const
{
return current + 1;
......
......@@ -713,6 +713,7 @@ public:
virtual unsigned caretMaxRenderedOffset() const;
virtual int previousOffset(int current) const;
virtual int previousOffsetForBackwardDeletion(int current) const;
virtual int nextOffset(int current) const;
virtual void imageChanged(CachedImage*, const IntRect* = 0);
......
......@@ -1168,6 +1168,106 @@ int RenderText::previousOffset(int current) const
return result;
}
#define HANGUL_CHOSEONG_START (0x1100)
#define HANGUL_CHOSEONG_END (0x115F)
#define HANGUL_JUNGSEONG_START (0x1160)
#define HANGUL_JUNGSEONG_END (0x11A2)
#define HANGUL_JONGSEONG_START (0x11A8)
#define HANGUL_JONGSEONG_END (0x11F9)
#define HANGUL_SYLLABLE_START (0xAC00)
#define HANGUL_SYLLABLE_END (0xD7AF)
#define HANGUL_JONGSEONG_COUNT (28)
enum HangulState {
HangulStateL,
HangulStateV,
HangulStateT,
HangulStateLV,
HangulStateLVT,
HangulStateBreak
};
inline bool isHangulLVT(UChar32 character)
{
return (character - HANGUL_SYLLABLE_START) % HANGUL_JONGSEONG_COUNT;
}
int RenderText::previousOffsetForBackwardDeletion(int current) const
{
#if PLATFORM(MAC)
UChar32 character;
while (current > 0) {
if (U16_IS_TRAIL((*m_text)[--current]))
--current;
if (current < 0)
break;
UChar32 character = m_text->characterStartingAt(current);
// We don't combine characters in Armenian ... Limbu range for backward deletion.
if ((character >= 0x0530) && (character < 0x1950))
break;
if (u_isbase(character) && (character != 0xFF9E) && (character != 0xFF9F))
break;
}
if (current <= 0)
return current;
// Hangul
character = m_text->characterStartingAt(current);
if (((character >= HANGUL_CHOSEONG_START) && (character <= HANGUL_JONGSEONG_END)) || ((character >= HANGUL_SYLLABLE_START) && (character <= HANGUL_SYLLABLE_END))) {
HangulState state;
HangulState initialState;
if (character < HANGUL_JUNGSEONG_START)
state = HangulStateL;
else if (character < HANGUL_JONGSEONG_START)
state = HangulStateV;
else if (character < HANGUL_SYLLABLE_START)
state = HangulStateT;
else
state = isHangulLVT(character) ? HangulStateLVT : HangulStateLV;
initialState = state;
while (current > 0 && ((character = m_text->characterStartingAt(current - 1)) >= HANGUL_CHOSEONG_START) && (character <= HANGUL_SYLLABLE_END) && ((character <= HANGUL_JONGSEONG_END) || (character >= HANGUL_SYLLABLE_START))) {
switch (state) {
case HangulStateV:
if (character <= HANGUL_CHOSEONG_END)
state = HangulStateL;
else if ((character >= HANGUL_SYLLABLE_START) && (character <= HANGUL_SYLLABLE_END) && !isHangulLVT(character))
state = HangulStateLV;
else if (character > HANGUL_JUNGSEONG_END)
state = HangulStateBreak;
break;
case HangulStateT:
if ((character >= HANGUL_JUNGSEONG_START) && (character <= HANGUL_JUNGSEONG_END))
state = HangulStateV;
else if ((character >= HANGUL_SYLLABLE_START) && (character <= HANGUL_SYLLABLE_END))
state = (isHangulLVT(character) ? HangulStateLVT : HangulStateLV);
else if (character < HANGUL_JUNGSEONG_START)
state = HangulStateBreak;
break;
default:
state = (character < HANGUL_JUNGSEONG_START) ? HangulStateL : HangulStateBreak;
break;
}
if (state == HangulStateBreak)
break;
--current;
}
}
return current;
#else
// Platforms other than Mac delete by one code point.
return current - 1;
#endif
}
int RenderText::nextOffset(int current) const
{
StringImpl* si = m_text.get();
......
......@@ -111,6 +111,7 @@ public:
virtual unsigned caretMaxRenderedOffset() const;
virtual int previousOffset(int current) const;
virtual int previousOffsetForBackwardDeletion(int current) const;
virtual int nextOffset(int current) const;
bool containsReversedText() const { return m_containsReversedText; }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment