Commit 02ba5929 authored by justing's avatar justing

<http://bugzilla.opendarwin.org/show_bug.cgi?id=4904>

        Bug with baseOffset and extentOffset in selections (for writely.com)
        <rdar://problem/4259818>
        selection object incorrect after double-clicking a word

        Reviewed by harrison

        Changed the Selection object's anchor/focus properties to match
        Mozilla's Selection object API, added getRangeAt and outlined the
        rest of the API.
        Cleaned up the JS Selection object (to do less work in the DOM binding).
        Added an updateLayout call to VisiblePosition::init and removed some
        updateLayout calls from other parts of the editing code.

        Added layout tests:
        * editing/selection/getRangeAt.html
        * editing/selection/anchor-focus1.html
        * editing/selection/anchor-focus2.html
        * editing/selection/anchor-focus3.html
        Updated expected results:
        * editing/selection/unrendered-001-expected.txt

        * khtml/ecma/kjs_window.cpp:
        (KJS::Selection::getValueProperty):
        (KJS::Selection::toString):
        (KJS::SelectionFunc::callAsFunction):
        * khtml/ecma/kjs_window.h:
        (KJS::Selection::):
        * khtml/editing/SelectionController.cpp:
        (khtml::SelectionController::modify):
        (khtml::SelectionController::type):
        (khtml::SelectionController::toString):
        (khtml::SelectionController::getRangeAt):
        (khtml::SelectionController::setBaseAndExtent):
        (khtml::SelectionController::setPosition):
        (khtml::SelectionController::collapse):
        (khtml::SelectionController::collapseToEnd):
        (khtml::SelectionController::collapseToStart):
        (khtml::SelectionController::empty):
        (khtml::SelectionController::extend):
        (khtml::SelectionController::validate):
        * khtml/editing/SelectionController.h:
        (khtml::SelectionController::part):
        (khtml::SelectionController::baseNode):
        (khtml::SelectionController::extentNode):
        (khtml::SelectionController::baseOffset):
        (khtml::SelectionController::extentOffset):
        (khtml::SelectionController::anchorNode):
        (khtml::SelectionController::anchorOffset):
        (khtml::SelectionController::focusNode):
        (khtml::SelectionController::focusOffset):
        (khtml::SelectionController::isCollapsed):
        * khtml/editing/visible_position.cpp:
        (khtml::VisiblePosition::init):
        (khtml::VisiblePosition::isCandidate):
        * khtml/khtml_part.cpp:
        (KHTMLPart::handleMousePressEventSingleClick):


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@11857 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 91734bd8
2006-01-03 Justin Garcia <justin.garcia@apple.com>
<http://bugzilla.opendarwin.org/show_bug.cgi?id=4904>
Bug with baseOffset and extentOffset in selections (for writely.com)
<rdar://problem/4259818>
selection object incorrect after double-clicking a word
Reviewed by harrison
Changed the Selection object's anchor/focus properties to match
Mozilla's Selection object API, added getRangeAt and outlined the
rest of the API.
Cleaned up the JS Selection object (to do less work in the DOM binding).
Added an updateLayout call to VisiblePosition::init and removed some
updateLayout calls from other parts of the editing code.
Added layout tests:
* editing/selection/getRangeAt.html
* editing/selection/anchor-focus1.html
* editing/selection/anchor-focus2.html
* editing/selection/anchor-focus3.html
Updated expected results:
* editing/selection/unrendered-001-expected.txt
* khtml/ecma/kjs_window.cpp:
(KJS::Selection::getValueProperty):
(KJS::Selection::toString):
(KJS::SelectionFunc::callAsFunction):
* khtml/ecma/kjs_window.h:
(KJS::Selection::):
* khtml/editing/SelectionController.cpp:
(khtml::SelectionController::modify):
(khtml::SelectionController::type):
(khtml::SelectionController::toString):
(khtml::SelectionController::getRangeAt):
(khtml::SelectionController::setBaseAndExtent):
(khtml::SelectionController::setPosition):
(khtml::SelectionController::collapse):
(khtml::SelectionController::collapseToEnd):
(khtml::SelectionController::collapseToStart):
(khtml::SelectionController::empty):
(khtml::SelectionController::extend):
(khtml::SelectionController::validate):
* khtml/editing/SelectionController.h:
(khtml::SelectionController::part):
(khtml::SelectionController::baseNode):
(khtml::SelectionController::extentNode):
(khtml::SelectionController::baseOffset):
(khtml::SelectionController::extentOffset):
(khtml::SelectionController::anchorNode):
(khtml::SelectionController::anchorOffset):
(khtml::SelectionController::focusNode):
(khtml::SelectionController::focusOffset):
(khtml::SelectionController::isCollapsed):
* khtml/editing/visible_position.cpp:
(khtml::VisiblePosition::init):
(khtml::VisiblePosition::isCandidate):
* khtml/khtml_part.cpp:
(KHTMLPart::handleMousePressEventSingleClick):
2006-01-03 Anders Carlsson <andersca@mac.com>
Reviewed by Maciej.
......
......@@ -2314,10 +2314,10 @@ const ClassInfo Selection::info = { "Selection", 0, 0, 0 };
anchorOffset Selection::AnchorOffset DontDelete|ReadOnly
focusNode Selection::FocusNode DontDelete|ReadOnly
focusOffset Selection::FocusOffset DontDelete|ReadOnly
baseNode Selection::AnchorNode DontDelete|ReadOnly
baseOffset Selection::AnchorOffset DontDelete|ReadOnly
extentNode Selection::FocusNode DontDelete|ReadOnly
extentOffset Selection::FocusOffset DontDelete|ReadOnly
baseNode Selection::BaseNode DontDelete|ReadOnly
baseOffset Selection::BaseOffset DontDelete|ReadOnly
extentNode Selection::ExtentNode DontDelete|ReadOnly
extentOffset Selection::ExtentOffset DontDelete|ReadOnly
isCollapsed Selection::IsCollapsed DontDelete|ReadOnly
type Selection::_Type DontDelete|ReadOnly
[[==]] Selection::EqualEqual DontDelete|ReadOnly
......@@ -2329,6 +2329,7 @@ const ClassInfo Selection::info = { "Selection", 0, 0, 0 };
setBaseAndExtent Selection::SetBaseAndExtent DontDelete|Function 4
setPosition Selection::SetPosition DontDelete|Function 2
modify Selection::Modify DontDelete|Function 3
getRangeAt Selection::GetRangeAt DontDelete|Function 1
@end
*/
KJS_IMPLEMENT_PROTOFUNC(SelectionFunc)
......@@ -2338,44 +2339,36 @@ Selection::Selection(KHTMLPart *p) : m_part(p)
JSValue *Selection::getValueProperty(ExecState *exec, int token) const
{
const Window* window = Window::retrieveWindow(m_part);
if (!window) {
return jsUndefined();
}
DocumentImpl *docimpl = m_part->xmlDocImpl();
if (docimpl)
docimpl->updateLayoutIgnorePendingStylesheets();
switch (token) {
case AnchorNode:
case BaseNode:
return getDOMNode(exec, m_part->selection().base().node());
case AnchorOffset:
case BaseOffset:
return jsNumber(m_part->selection().base().offset());
case FocusNode:
case ExtentNode:
return getDOMNode(exec, m_part->selection().extent().node());
case FocusOffset:
case ExtentOffset:
return jsNumber(m_part->selection().extent().offset());
case IsCollapsed:
return jsBoolean(!m_part->selection().isRange());
case _Type: {
switch (m_part->selection().state()) {
case khtml::SelectionController::NONE:
return jsString("None");
case khtml::SelectionController::CARET:
return jsString("Caret");
case khtml::SelectionController::RANGE:
return jsString("Range");
}
}
default:
assert(0);
return jsUndefined();
}
SelectionController s = m_part->selection();
const Window* window = Window::retrieveWindow(m_part);
if (!window)
return jsUndefined();
switch (token) {
case AnchorNode:
return getDOMNode(exec, s.anchorNode());
case BaseNode:
return getDOMNode(exec, s.baseNode());
case AnchorOffset:
return jsNumber(s.anchorOffset());
case BaseOffset:
return jsNumber(s.baseOffset());
case FocusNode:
return getDOMNode(exec, s.focusNode());
case ExtentNode:
return getDOMNode(exec, s.extentNode());
case FocusOffset:
return jsNumber(s.focusOffset());
case ExtentOffset:
return jsNumber(s.extentOffset());
case IsCollapsed:
return jsBoolean(s.isCollapsed());
case _Type:
return jsString(s.type());
default:
assert(0);
return jsUndefined();
}
}
bool Selection::getOwnPropertySlot(ExecState *exec, const Identifier& propertyName, PropertySlot& slot)
......@@ -2393,7 +2386,7 @@ JSValue *Selection::toPrimitive(ExecState *exec, Type) const
UString Selection::toString(ExecState *) const
{
return UString(m_part->selectedText());
return UString((m_part->selection()).toString());
}
JSValue *SelectionFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args)
......@@ -2403,65 +2396,36 @@ JSValue *SelectionFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const
Selection *selection = static_cast<Selection *>(thisObj);
KHTMLPart *part = selection->part();
if (part) {
DocumentImpl *docimpl = part->xmlDocImpl();
if (docimpl)
docimpl->updateLayoutIgnorePendingStylesheets();
SelectionController s = part->selection();
switch (id) {
case Selection::Collapse:
TypingCommand::closeTyping(part->lastEditCommand());
part->setSelection(khtml::SelectionController(Position(toNode(args[0]), args[1]->toInt32(exec)), khtml::SEL_DEFAULT_AFFINITY));
s.collapse(toNode(args[0]), args[1]->toInt32(exec));
break;
case Selection::CollapseToEnd:
TypingCommand::closeTyping(part->lastEditCommand());
part->setSelection(khtml::SelectionController(part->selection().end(), part->selection().endAffinity()));
s.collapseToEnd();
break;
case Selection::CollapseToStart:
TypingCommand::closeTyping(part->lastEditCommand());
part->setSelection(khtml::SelectionController(part->selection().start(), part->selection().startAffinity()));
s.collapseToStart();
break;
case Selection::Empty:
TypingCommand::closeTyping(part->lastEditCommand());
part->clearSelection();
s.empty();
break;
case Selection::SetBaseAndExtent: {
TypingCommand::closeTyping(part->lastEditCommand());
Position base(toNode(args[0]), args[1]->toInt32(exec));
Position extent(toNode(args[2]), args[3]->toInt32(exec));
part->setSelection(khtml::SelectionController(base, khtml::SEL_DEFAULT_AFFINITY, extent, khtml::SEL_DEFAULT_AFFINITY));
case Selection::SetBaseAndExtent:
s.setBaseAndExtent(toNode(args[0]), args[1]->toInt32(exec), toNode(args[2]), args[3]->toInt32(exec));
break;
}
case Selection::SetPosition:
TypingCommand::closeTyping(part->lastEditCommand());
part->setSelection(khtml::SelectionController(Position(toNode(args[0]), args[1]->toInt32(exec)), khtml::SEL_DEFAULT_AFFINITY));
s.setPosition(toNode(args[0]), args[1]->toInt32(exec));
break;
case Selection::Modify:
s.modify(args[0]->toString(exec).domString(), args[1]->toString(exec).domString(), args[2]->toString(exec).domString());
break;
case Selection::GetRangeAt:
return getDOMRange(exec, s.getRangeAt(args[0]->toInt32(exec)).get());
break;
case Selection::Modify: {
TypingCommand::closeTyping(part->lastEditCommand());
khtml::SelectionController s(part->selection());
khtml::SelectionController::EAlter alter = khtml::SelectionController::MOVE;
if (args[0]->toString(exec).domString().lower() == "extend")
alter = khtml::SelectionController::EXTEND;
DOMString directionString = args[1]->toString(exec).domString().lower();
khtml::SelectionController::EDirection direction = khtml::SelectionController::FORWARD;
if (directionString == "backward")
direction = khtml::SelectionController::BACKWARD;
else if (directionString == "left")
direction = khtml::SelectionController::LEFT;
if (directionString == "right")
direction = khtml::SelectionController::RIGHT;
khtml::ETextGranularity granularity = khtml::CHARACTER;
DOMString granularityString = args[2]->toString(exec).domString().lower();
if (granularityString == "word")
granularity = khtml::WORD;
else if (granularityString == "line")
granularity = khtml::LINE;
else if (granularityString == "paragraph")
granularity = khtml::PARAGRAPH;
s.modify(alter, direction, granularity);
part->setSelection(s);
part->setSelectionGranularity(granularity);
}
}
part->setSelection(s);
}
return jsUndefined();
......
......@@ -266,7 +266,7 @@ namespace KJS {
virtual UString toString(ExecState*) const;
enum { AnchorNode, AnchorOffset, FocusNode, FocusOffset, BaseNode, BaseOffset, ExtentNode, ExtentOffset,
IsCollapsed, _Type, EqualEqual, Collapse, CollapseToEnd, CollapseToStart, Empty, ToString,
SetBaseAndExtent, SetPosition, Modify };
SetBaseAndExtent, SetPosition, Modify, GetRangeAt };
KHTMLPart *part() const { return m_part; }
virtual const ClassInfo* classInfo() const { return &info; }
static const ClassInfo info;
......
......@@ -363,8 +363,51 @@ VisiblePosition SelectionController::modifyMovingLeftBackward(ETextGranularity g
return pos;
}
bool SelectionController::modify(const DOMString &alterString, const DOMString &directionString, const DOMString &granularityString)
{
DOMString alterStringLower = alterString.lower();
EAlter alter;
if (alterStringLower == "extend")
alter = EXTEND;
else if (alterStringLower == "move")
alter = MOVE;
else
return false;
DOMString directionStringLower = directionString.lower();
EDirection direction;
if (directionStringLower == "forward")
direction = FORWARD;
else if (directionStringLower == "backward")
direction = BACKWARD;
else if (directionStringLower == "left")
direction = LEFT;
else if (directionStringLower == "right")
direction = RIGHT;
else
return false;
DOMString granularityStringLower = granularityString.lower();
ETextGranularity granularity;
if (granularityStringLower == "character")
granularity = CHARACTER;
else if (granularityStringLower == "word")
granularity = WORD;
else if (granularityStringLower == "line")
granularity = LINE;
else if (granularityStringLower == "paragraph")
granularity = PARAGRAPH;
else
return false;
return modify(alter, direction, granularity);
}
bool SelectionController::modify(EAlter alter, EDirection dir, ETextGranularity granularity)
{
if (part())
part()->setSelectionGranularity(granularity);
setModifyBias(alter, dir);
VisiblePosition pos;
......@@ -563,15 +606,6 @@ void SelectionController::setExtent(const VisiblePosition &pos)
validate();
}
void SelectionController::setBaseAndExtent(const VisiblePosition &base, const VisiblePosition &extent)
{
// FIXME: Support extentAffinity
m_affinity = base.affinity();
m_base = base.deepEquivalent();
m_extent = extent.deepEquivalent();
validate();
}
void SelectionController::setBase(const Position &pos, EAffinity baseAffinity)
{
m_affinity = baseAffinity;
......@@ -587,15 +621,6 @@ void SelectionController::setExtent(const Position &pos, EAffinity extentAffinit
validate();
}
void SelectionController::setBaseAndExtent(const Position &base, EAffinity baseAffinity, const Position &extent, EAffinity extentAffinity)
{
// FIXME: extentAffinity
m_affinity = baseAffinity;
m_base = base;
m_extent = extent;
validate();
}
void SelectionController::setNeedsLayout(bool flag)
{
m_needsLayout = flag;
......@@ -661,6 +686,64 @@ PassRefPtr<RangeImpl> SelectionController::toRange() const
return result;
}
DOMString SelectionController::type() const
{
if (isNone())
return DOMString("None");
else if (isCaret())
return DOMString("Caret");
else
return DOMString("Range");
}
DOMString SelectionController::toString() const
{
return DOMString(plainText(toRange().get()));
}
PassRefPtr<RangeImpl> SelectionController::getRangeAt(int index) const
{
return index == 0 ? PassRefPtr<RangeImpl>(toRange()) : PassRefPtr<RangeImpl>();
}
void SelectionController::setBaseAndExtent(NodeImpl *baseNode, int baseOffset, NodeImpl *extentNode, int extentOffset)
{
VisiblePosition visibleBase = VisiblePosition(baseNode, baseOffset, DOWNSTREAM);
VisiblePosition visibleExtent = VisiblePosition(extentNode, extentOffset, DOWNSTREAM);
moveTo(visibleBase, visibleExtent);
}
void SelectionController::setPosition(NodeImpl *node, int offset)
{
moveTo(VisiblePosition(node, offset, DOWNSTREAM));
}
void SelectionController::collapse(NodeImpl *node, int offset)
{
moveTo(VisiblePosition(node, offset, DOWNSTREAM));
}
void SelectionController::collapseToEnd()
{
moveTo(VisiblePosition(m_end, DOWNSTREAM));
}
void SelectionController::collapseToStart()
{
moveTo(VisiblePosition(m_start, DOWNSTREAM));
}
void SelectionController::empty()
{
moveTo(SelectionController());
}
void SelectionController::extend(NodeImpl *node, int offset)
{
moveTo(VisiblePosition(node, offset, DOWNSTREAM));
}
void SelectionController::layout()
{
if (isNone() || !m_start.node()->inDocument() || !m_end.node()->inDocument()) {
......@@ -832,17 +915,12 @@ void SelectionController::validate(ETextGranularity granularity)
// Move the selection to rendered positions, if possible.
Position originalBase(m_base);
bool baseAndExtentEqual = m_base == m_extent;
bool updatedLayout = false;
if (m_base.isNotNull()) {
m_base.node()->getDocument()->updateLayout();
updatedLayout = true;
m_base = VisiblePosition(m_base, m_affinity).deepEquivalent();
if (baseAndExtentEqual)
m_extent = m_base;
}
if (m_extent.isNotNull() && !baseAndExtentEqual) {
if (!updatedLayout)
m_extent.node()->getDocument()->updateLayout();
m_extent = VisiblePosition(m_extent, m_affinity).deepEquivalent();
}
......
......@@ -31,6 +31,7 @@
#include "xml/dom_position.h"
#include "text_granularity.h"
#include "misc/shared.h"
#include "editing/visible_text.h"
class KHTMLPart;
class QPainter;
......@@ -50,6 +51,7 @@ public:
typedef DOM::Position Position;
typedef DOM::RangeImpl RangeImpl;
typedef DOM::NodeImpl NodeImpl;
SelectionController();
SelectionController(const RangeImpl *, EAffinity baseAffinity, EAffinity extentAffinity);
......@@ -83,12 +85,9 @@ public:
void clear();
void setBase(const VisiblePosition &);
void setExtent(const VisiblePosition &);
void setBaseAndExtent(const VisiblePosition &base, const VisiblePosition &extent);
void setBase(const Position &pos, EAffinity affinity);
void setExtent(const VisiblePosition &);
void setExtent(const Position &pos, EAffinity affinity);
void setBaseAndExtent(const Position &base, EAffinity baseAffinity, const Position &extent, EAffinity extentAffinity);
Position base() const { return m_base; }
Position extent() const { return m_extent; }
......@@ -112,6 +111,47 @@ public:
void debugRenderer(khtml::RenderObject *r, bool selected) const;
friend class ::KHTMLPart;
KHTMLPart *part() const { return !isNone() ? m_start.node()->getDocument()->part() : 0; }
// Safari Selection Object API
NodeImpl *baseNode() const { return m_base.node(); }
NodeImpl *extentNode() const { return m_extent.node(); }
int baseOffset() const { return m_base.offset(); }
int extentOffset() const { return m_extent.offset(); }
DOM::DOMString type() const;
void setBaseAndExtent(NodeImpl *baseNode, int baseOffset, NodeImpl *extentNode, int extentOffset);
void setPosition(NodeImpl *node, int offset);
bool modify(const DOM::DOMString &alterString, const DOM::DOMString &directionString, const DOM::DOMString &granularityString);
// Mozilla Selection Object API
// In FireFox, anchor/focus are the equal to the start/end of the selection,
// but reflect the direction in which the selection was made by the user. That does
// not mean that they are base/extent, since the base/extent don't reflect
// expansion.
NodeImpl *anchorNode() const { return m_baseIsFirst ? m_start.node() : m_end.node(); }
int anchorOffset() const { return m_baseIsFirst ? m_start.offset() : m_end.offset(); }
NodeImpl *focusNode() const { return m_baseIsFirst ? m_end.node() : m_start.node(); }
int focusOffset() const { return m_baseIsFirst ? m_end.offset() : m_start.offset(); }
bool isCollapsed() const { return !isRange(); }
DOM::DOMString toString() const;
void collapse(NodeImpl *node, int offset);
void collapseToEnd();
void collapseToStart();
void extend(NodeImpl *node, int offset);
PassRefPtr<RangeImpl> getRangeAt(int index) const;
//void deleteFromDocument();
//bool containsNode(NodeImpl *node, bool entirelyContained);
//int rangeCount() const;
//void addRange(const RangeImpl *);
//void selectAllChildren(const NodeImpl *);
//void removeRange(const RangeImpl *);
//void removeAllRanges();
// Microsoft Selection Object API
void empty();
//void clear();
//TextRange *createRange();
#ifndef NDEBUG
void formatForDebugger(char *buffer, unsigned length) const;
......
......@@ -71,8 +71,13 @@ void VisiblePosition::init(const Position &pos, EAffinity affinity)
{
m_affinity = affinity;
if (pos.isNull()) {
m_deepPosition = Position();
return;
}
pos.node()->getDocument()->updateLayoutIgnorePendingStylesheets();
Position deepPos = deepEquivalent(pos);
if (isCandidate(deepPos)) {
m_deepPosition = deepPos;
Position previous = previousVisiblePosition(deepPos);
......@@ -217,7 +222,6 @@ bool VisiblePosition::isCandidate(const Position &pos)
return false;
if (renderer->isReplaced())
// return true for replaced elements
return pos.offset() == 0 || pos.offset() == 1;
if (renderer->isBR()) {
......
......@@ -2459,9 +2459,9 @@ void KHTMLPart::handleMousePressEventSingleClick(khtml::MousePressEvent *event)
Position start = sel.start();
short before = RangeImpl::compareBoundaryPoints(pos.node(), pos.offset(), start.node(), start.offset());
if (before <= 0) {
sel.setBaseAndExtent(pos, visiblePos.affinity(), sel.end(), sel.endAffinity());
sel.setBaseAndExtent(pos.node(), pos.offset(), sel.end().node(), sel.end().offset());
} else {
sel.setBaseAndExtent(start, sel.startAffinity(), pos, visiblePos.affinity());
sel.setBaseAndExtent(start.node(), start.offset(), pos.node(), pos.offset());
}
if (d->m_selectionGranularity != CHARACTER) {
......
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