+2004-04-06 Ken Kocienda <kocienda@apple.com>
+
+ Reviewed by Dave
+
+ Added execCommand feature.
+ Added Javascript selection object.
+ This lays the groundwork for layout tests for editing.
+
+ * khtml/dom/dom_doc.cpp:
+ (DOM::Document::execCommand): Added. Calls through to impl's execCommand.
+ * khtml/dom/dom_doc.h: Added execCommand declaration.
+ * khtml/ecma/kjs_dom.cpp:
+ (DOMDocumentProtoFunc::tryCall): Switch on new ExecCommand constant and call through to the document.
+ * khtml/ecma/kjs_dom.h: Added ExecCommand constant.
+ (KJS::DOMDocument::):
+ * khtml/ecma/kjs_dom.lut.h: Generated file.
+ * khtml/ecma/kjs_window.cpp:
+ (Window::Window): Initialize selection object.
+ (Window::selection): Return window's selection object.
+ (Window::mark): Mark selection object.
+ (WindowFunc::tryCall): Return selection object on GetSelection.
+ (LocationFunc::tryCall): Added.
+ (Selection::Selection): Added.
+ (Selection::~Selection): Added.
+ (Selection::get): Added.
+ (Selection::put): Added.
+ (Selection::toPrimitive): Added.
+ (Selection::toString): Added.
+ (SelectionFunc::tryCall): Added.
+ * khtml/ecma/kjs_window.h:
+ (KJS::Selection::): Added.
+ (KJS::Selection::part): Added.
+ (KJS::Selection::classInfo): Added.
+ * khtml/ecma/kjs_window.lut.h: Generated file.
+ * khtml/editing/htmlediting_impl.cpp:
+ (debugPosition): Debugging aid.
+ (DeleteCollapsibleWhitespaceCommandImpl::deleteWhitespace): Move the ending position
+ if you are about to delete it. Fixes a crasher I discovered while writing tests.
+ (DeleteSelectionCommandImpl::doApply): Move to containing editable block position 0
+ instead of 1 in a block in delete case 1.
+ (InputTextCommandImpl::prepareForTextInsertion):
+ (TypingCommandImpl::issueCommandForDeleteKey):
+ * khtml/khtml_part.h:
+ * khtml/khtml_selection.cpp:
+ (KHTMLSelection::validate): Now adjusts the selection down to leaf nodes if needed.
+ (KHTMLSelection::debugPosition): Debugging aid.
+ * khtml/xml/dom_docimpl.cpp:
+ (DocumentImpl::execCommand): Added. Supports five different commands.
+ * khtml/xml/dom_docimpl.h:
+ * khtml/xml/dom_nodeimpl.cpp:
+ (NodeImpl::previousEditable): Use false instead of 0 for equality check.
+ (NodeImpl::nextEditable): Ditto.
+ * khtml/xml/dom_position.cpp:
+ (DOMPosition::equivalentLeafPosition): New function
+ (DOMPosition::previousRenderedEditablePosition): New function
+ (DOMPosition::nextRenderedEditablePosition): New function
+ (DOMPosition::equivalentUpstreamPosition): Refined behavior to handle more cases correctly.
+ (DOMPosition::equivalentDownstreamPosition): Ditto.
+ (DOMPosition::atStartOfContainingEditableBlock):New function
+ (DOMPosition::atStartOfRootEditableBlock):New function
+ * khtml/xml/dom_position.h:
+ * kwq/KWQKHTMLPart.h:
+ * kwq/KWQKHTMLPart.mm:
+ (KWQKHTMLPart::issueUndoCommand): New function for calling undo programatically.
+ (KWQKHTMLPart::issueRedoCommand): Ditto, but for redo.
+ * kwq/KWQRenderTreeDebug.cpp:
+ (nodePositionRelativeToRoot): New function to generate log information for the selection.
+ (writeSelection): Writes the selection if there is one.
+ (externalRepresentation): Calls writeSelection
+ * kwq/WebCoreBridge.h: New declarations for issueUndoCommand and issueRedoCommand.
+
2004-04-05 Darin Adler <darin@apple.com>
* khtml/html/kentities.gperf: Added © and ®, both supported by Gecko
--- /dev/null
+#include <dom_position.h>
refType = 4;
sourceTree = "<group>";
};
- 832557C4061E3172007B8054 = {
- fileRef = 832557C3061E3172007B8054;
- isa = PBXBuildFile;
- settings = {
- };
- };
//830
//831
//832
return r;
}
+bool Document::execCommand(const DOMString &command, bool userInterface, const DOMString &value)
+{
+ if (!impl)
+ throw DOMException(DOMException::NOT_FOUND_ERR);
+
+ return static_cast<DocumentImpl*>(impl)->execCommand(command, userInterface, value);
+}
+
// ----------------------------------------------------------------------------
DocumentFragment::DocumentFragment() : Node()
DOMString toString() const;
+
+ /**
+ * not part of the DOM
+ *
+ * executes an editing command
+ */
+ bool execCommand(const DOMString &command, bool userInterface, const DOMString &value);
+
Document( DocumentImpl *i);
protected:
createTreeWalker DOMDocument::CreateTreeWalker DontDelete|Function 4
createEvent DOMDocument::CreateEvent DontDelete|Function 1
getOverrideStyle DOMDocument::GetOverrideStyle DontDelete|Function 2
+ execCommand DOMDocument::ExecCommand DontDelete|Function 3
@end
*/
DEFINE_PROTOTYPE("DOMDocument", DOMDocumentProto)
else
return getDOMCSSStyleDeclaration(exec,doc.getOverrideStyle(static_cast<DOM::Element>(arg0),args[1].toString(exec).string()));
}
+ case DOMDocument::ExecCommand: {
+ return Boolean(doc.execCommand(args[0].toString(exec).string(), args[1].toBoolean(exec), args[2].toString(exec).string()));
+ }
default:
break;
}
CreateAttributeNS, GetElementsByTagNameNS, GetElementById,
CreateRange, CreateNodeIterator, CreateTreeWalker, DefaultView,
CreateEvent, StyleSheets, PreferredStylesheetSet,
- SelectedStylesheetSet, GetOverrideStyle, ReadyState };
+ SelectedStylesheetSet, GetOverrideStyle, ReadyState, ExecCommand };
};
class DOMAttr : public DOMNode {
{ "createComment", DOMDocument::CreateComment, DontDelete|Function, 1, &DOMDocumentProtoTableEntries[27] },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
- { "createNodeIterator", DOMDocument::CreateNodeIterator, DontDelete|Function, 3, 0 },
+ { "createNodeIterator", DOMDocument::CreateNodeIterator, DontDelete|Function, 3, &DOMDocumentProtoTableEntries[30] },
{ 0, 0, 0, 0, 0 },
{ "importNode", DOMDocument::ImportNode, DontDelete|Function, 2, 0 },
{ "createElementNS", DOMDocument::CreateElementNS, DontDelete|Function, 2, 0 },
{ "getElementById", DOMDocument::GetElementById, DontDelete|Function, 1, 0 },
{ "createRange", DOMDocument::CreateRange, DontDelete|Function, 0, 0 },
{ "createEvent", DOMDocument::CreateEvent, DontDelete|Function, 1, 0 },
- { "getOverrideStyle", DOMDocument::GetOverrideStyle, DontDelete|Function, 2, 0 }
+ { "getOverrideStyle", DOMDocument::GetOverrideStyle, DontDelete|Function, 2, 0 },
+ { "execCommand", DOMDocument::ExecCommand, DontDelete|Function, 3, 0 }
};
-const struct HashTable DOMDocumentProtoTable = { 2, 30, DOMDocumentProtoTableEntries, 23 };
+const struct HashTable DOMDocumentProtoTable = { 2, 31, DOMDocumentProtoTableEntries, 23 };
} // namespace
#include "khtmlview.h"
#include "khtml_part.h"
+#include "khtml_selection.h"
+#include "dom/dom_string.h"
+#include "dom/dom_node.h"
+#include "editing/htmlediting.h"
#include "xml/dom2_eventsimpl.h"
#include "xml/dom_docimpl.h"
#include "html/html_documentimpl.h"
+using DOM::DocumentImpl;
+using DOM::DOMString;
+using DOM::Node;
+using khtml::TypingCommand;
+
using namespace KJS;
namespace KJS {
IMPLEMENT_PROTOFUNC(WindowFunc)
Window::Window(KHTMLPart *p)
- : ObjectImp(/*no proto*/), m_part(p), screen(0), history(0), frames(0), loc(0), m_evt(0)
+ : ObjectImp(/*no proto*/), m_part(p), screen(0), history(0), frames(0), loc(0), m_selection(0), m_evt(0)
{
winq = new WindowQObject(this);
//kdDebug(6070) << "Window::Window this=" << this << " part=" << m_part << " " << m_part->name() << endl;
return loc;
}
+Selection *Window::selection() const
+{
+ if (!m_selection)
+ const_cast<Window*>(this)->m_selection = new Selection(m_part);
+ return m_selection;
+}
+
// reference our special objects during garbage collection
void Window::mark()
{
//kdDebug(6070) << "Window::mark " << this << " marking loc=" << loc << endl;
if (loc && !loc->marked())
loc->mark();
+ if (m_selection && !m_selection->marked())
+ m_selection->mark();
}
bool Window::hasProperty(ExecState * /*exec*/, const Identifier &/*p*/) const
case Window::GetSelection:
if (!window->isSafeScript(exec))
return Undefined();
- return String(part->selectedText());
+ return Value(window->selection());
case Window::Blur:
#if APPLE_CHANGES
KWQ(part)->unfocusWindow();
return Undefined();
}
+////////////////////// Selection Object ////////////////////////
+
+const ClassInfo Selection::info = { "Selection", 0, 0, 0 };
+/*
+@begin SelectionTable 19
+ anchorNode Selection::AnchorNode DontDelete|ReadOnly
+ 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
+ isCollapsed Selection::IsCollapsed DontDelete|ReadOnly
+ type Selection::_Type DontDelete|ReadOnly
+ [[==]] Selection::EqualEqual DontDelete|ReadOnly
+ toString Selection::ToString DontDelete|Function 0
+ collapse Selection::Collapse DontDelete|Function 2
+ collapseToEnd Selection::CollapseToEnd DontDelete|Function 0
+ collapseToStart Selection::CollapseToStart DontDelete|Function 0
+ empty Selection::Empty DontDelete|Function 0
+ setBaseAndExtent Selection::SetBaseAndExtent DontDelete|Function 4
+ setPosition Selection::SetPosition DontDelete|Function 2
+ modify Selection::Modify DontDelete|Function 3
+@end
+*/
+IMPLEMENT_PROTOFUNC(SelectionFunc)
+Selection::Selection(KHTMLPart *p) : m_part(p)
+{
+ //kdDebug(6070) << "Selection::Selection " << this << " m_part=" << (void*)m_part << endl;
+}
+
+Selection::~Selection()
+{
+ //kdDebug(6070) << "Selection::~Selection " << this << " m_part=" << (void*)m_part << endl;
+}
+
+Value Selection::get(ExecState *exec, const Identifier &p) const
+{
+#ifdef KJS_VERBOSE
+ kdDebug(6070) << "Selection::get " << p.qstring() << " m_part=" << (void*)m_part << endl;
+#endif
+
+ if (m_part.isNull())
+ return Undefined();
+
+ const Window* window = Window::retrieveWindow(m_part);
+ if (!window || !window->isSafeScript(exec))
+ return Undefined();
+
+ DocumentImpl *docimpl = m_part->xmlDocImpl();
+ if (docimpl)
+ docimpl->updateLayout();
+
+ KURL url = m_part->url();
+ const HashEntry *entry = Lookup::findEntry(&SelectionTable, p);
+ if (entry)
+ switch (entry->value) {
+ case AnchorNode:
+ case BaseNode:
+ return getDOMNode(exec, Node(m_part->selection().baseNode()));
+ case AnchorOffset:
+ case BaseOffset:
+ return Number(m_part->selection().baseOffset());
+ case FocusNode:
+ case ExtentNode:
+ return getDOMNode(exec, Node(m_part->selection().extentNode()));
+ case FocusOffset:
+ case ExtentOffset:
+ return Number(m_part->selection().extentOffset());
+ case IsCollapsed:
+ return Boolean(m_part->selection().state() == KHTMLSelection::CARET);
+ case _Type: {
+ switch (m_part->selection().state()) {
+ case KHTMLSelection::NONE:
+ return String("None");
+ case KHTMLSelection::CARET:
+ return String("Caret");
+ case KHTMLSelection::RANGE:
+ return String("Range");
+ }
+ }
+ case EqualEqual:
+ return String(toString(exec));
+ case ToString:
+ return lookupOrCreateFunction<SelectionFunc>(exec,p,this,entry->value,entry->params,entry->attr);
+ }
+ // Look for overrides
+ ValueImp * val = ObjectImp::getDirect(p);
+ if (val)
+ return Value(val);
+ if (entry)
+ switch (entry->value) {
+ case Collapse:
+ case CollapseToEnd:
+ case CollapseToStart:
+ case Empty:
+ case SetBaseAndExtent:
+ case SetPosition:
+ case Modify:
+ return lookupOrCreateFunction<SelectionFunc>(exec,p,this,entry->value,entry->params,entry->attr);
+ }
+
+ return Undefined();
+}
+
+void Selection::put(ExecState *exec, const Identifier &p, const Value &v, int attr)
+{
+}
+
+Value Selection::toPrimitive(ExecState *exec, Type) const
+{
+ return String(toString(exec));
+}
+
+UString Selection::toString(ExecState *) const
+{
+ if (m_part->selection().state() != KHTMLSelection::RANGE)
+ return UString("");
+ return UString(m_part->selection().toRange().toString());
+}
+
+Value SelectionFunc::tryCall(ExecState *exec, Object &thisObj, const List &args)
+{
+ if (!thisObj.inherits(&Selection::info)) {
+ Object err = Error::create(exec,TypeError);
+ exec->setException(err);
+ return err;
+ }
+ Selection *selection = static_cast<Selection *>(thisObj.imp());
+ KHTMLPart *part = selection->part();
+ if (part) {
+ DocumentImpl *docimpl = part->xmlDocImpl();
+ if (docimpl)
+ docimpl->updateLayout();
+
+ switch (id) {
+ case Selection::Collapse:
+ TypingCommand::closeTyping(part->lastEditCommand());
+ part->setSelection(KHTMLSelection(KJS::toNode(args[0]).handle(), args[1].toInt32(exec)));
+ break;
+ case Selection::CollapseToEnd:
+ TypingCommand::closeTyping(part->lastEditCommand());
+ part->setSelection(KHTMLSelection(part->selection().endPosition()));
+ break;
+ case Selection::CollapseToStart:
+ TypingCommand::closeTyping(part->lastEditCommand());
+ part->setSelection(KHTMLSelection(part->selection().startPosition()));
+ break;
+ case Selection::Empty:
+ TypingCommand::closeTyping(part->lastEditCommand());
+ part->clearSelection();
+ break;
+ case Selection::SetBaseAndExtent:
+ TypingCommand::closeTyping(part->lastEditCommand());
+ part->setSelection(KHTMLSelection(KJS::toNode(args[0]).handle(), args[1].toInt32(exec), KJS::toNode(args[2]).handle(), args[3].toInt32(exec)));
+ break;
+ case Selection::SetPosition:
+ TypingCommand::closeTyping(part->lastEditCommand());
+ part->setSelection(KHTMLSelection(KJS::toNode(args[0]).handle(), args[1].toInt32(exec)));
+ break;
+ case Selection::Modify: {
+ TypingCommand::closeTyping(part->lastEditCommand());
+ KHTMLSelection s(part->selection());
+ KHTMLSelection::EAlter alter = KHTMLSelection::MOVE;
+ if (args[0].toString(exec).string().lower() == "extend")
+ alter = KHTMLSelection::EXTEND;
+ DOMString directionString = args[1].toString(exec).string().lower();
+ KHTMLSelection::EDirection direction = KHTMLSelection::FORWARD;
+ if (directionString == "backward")
+ direction = KHTMLSelection::BACKWARD;
+ else if (directionString == "left")
+ direction = KHTMLSelection::LEFT;
+ if (directionString == "right")
+ direction = KHTMLSelection::RIGHT;
+ KHTMLSelection::ETextGranularity granularity = KHTMLSelection::CHARACTER;
+ DOMString granularityString = args[2].toString(exec).string().lower();
+ if (granularityString == "word")
+ granularity = KHTMLSelection::WORD;
+ else if (granularityString == "line")
+ granularity = KHTMLSelection::LINE;
+ s.modify(alter, direction, granularity);
+ part->setSelection(s);
+ }
+ }
+ }
+
+ return Undefined();
+}
+
////////////////////// History Object ////////////////////////
const ClassInfo History::info = { "History", 0, 0, 0 };
class WindowFunc;
class WindowQObject;
class Location;
+ class Selection;
class History;
class FrameArray;
class JSEventListener;
void scheduleClose();
bool isSafeScript(ExecState *exec) const;
Location *location() const;
+ Selection *selection() const;
JSEventListener *getJSEventListener(const Value &val, bool html = false);
JSLazyEventListener *getJSLazyEventListener(const QString &code, bool html = false);
void clear( ExecState *exec );
History *history;
FrameArray *frames;
Location *loc;
+ Selection *m_selection;
WindowQObject *winq;
DOM::Event *m_evt;
};
QGuardedPtr<KHTMLPart> m_part;
};
+ class Selection : public ObjectImp {
+ public:
+ ~Selection();
+ virtual Value get(ExecState *exec, const Identifier &propertyName) const;
+ virtual void put(ExecState *exec, const Identifier &propertyName, const Value &value, int attr = None);
+ virtual Value toPrimitive(ExecState *exec, Type preferred) const;
+ virtual UString toString(ExecState *exec) const;
+ enum { AnchorNode, AnchorOffset, FocusNode, FocusOffset, BaseNode, BaseOffset, ExtentNode, ExtentOffset,
+ IsCollapsed, _Type, EqualEqual, Collapse, CollapseToEnd, CollapseToStart, Empty, ToString,
+ SetBaseAndExtent, SetPosition, Modify };
+ KHTMLPart *part() const { return m_part; }
+ virtual const ClassInfo* classInfo() const { return &info; }
+ static const ClassInfo info;
+ private:
+ friend class Window;
+ Selection(KHTMLPart *p);
+ QGuardedPtr<KHTMLPart> m_part;
+ };
+
#ifdef Q_WS_QWS
class Konqueror : public ObjectImp {
friend class KonquerorFunc;
namespace KJS {
+const struct HashEntry SelectionTableEntries[] = {
+ { "focusOffset", Selection::FocusOffset, DontDelete|ReadOnly, 0, &SelectionTableEntries[20] },
+ { 0, 0, 0, 0, 0 },
+ { "modify", Selection::Modify, DontDelete|Function, 3, 0 },
+ { "focusNode", Selection::FocusNode, DontDelete|ReadOnly, 0, &SelectionTableEntries[19] },
+ { 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0 },
+ { "extentOffset", Selection::FocusOffset, DontDelete|ReadOnly, 0, 0 },
+ { "setPosition", Selection::SetPosition, DontDelete|Function, 2, 0 },
+ { "empty", Selection::Empty, DontDelete|Function, 0, 0 },
+ { "extentNode", Selection::FocusNode, DontDelete|ReadOnly, 0, 0 },
+ { 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0 },
+ { "type", Selection::_Type, DontDelete|ReadOnly, 0, 0 },
+ { "collapseToEnd", Selection::CollapseToEnd, DontDelete|Function, 0, &SelectionTableEntries[25] },
+ { "anchorOffset", Selection::AnchorOffset, DontDelete|ReadOnly, 0, &SelectionTableEntries[22] },
+ { 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0 },
+ { "anchorNode", Selection::AnchorNode, DontDelete|ReadOnly, 0, &SelectionTableEntries[21] },
+ { "baseNode", Selection::AnchorNode, DontDelete|ReadOnly, 0, &SelectionTableEntries[23] },
+ { "baseOffset", Selection::AnchorOffset, DontDelete|ReadOnly, 0, 0 },
+ { "isCollapsed", Selection::IsCollapsed, DontDelete|ReadOnly, 0, 0 },
+ { "[[==]]", Selection::EqualEqual, DontDelete|ReadOnly, 0, &SelectionTableEntries[24] },
+ { "toString", Selection::ToString, DontDelete|Function, 0, &SelectionTableEntries[26] },
+ { "collapse", Selection::Collapse, DontDelete|Function, 2, 0 },
+ { "collapseToStart", Selection::CollapseToStart, DontDelete|Function, 0, 0 },
+ { "setBaseAndExtent", Selection::SetBaseAndExtent, DontDelete|Function, 4, 0 }
+};
+
+const struct HashTable SelectionTable = { 2, 27, SelectionTableEntries, 19 };
+
+} // namespace
+
+namespace KJS {
+
const struct HashEntry HistoryTableEntries[] = {
{ 0, 0, 0, 0, 0 },
{ "back", History::Back, DontDelete|Function, 0, &HistoryTableEntries[4] },
void KHTMLSelection::validate(ETextGranularity expandTo)
{
+ // move the base and extent nodes to their equivalent leaf positions
+ bool baseAndExtentEqual = m_baseNode == m_extentNode && m_baseOffset == m_extentOffset;
+ if (m_baseNode) {
+ DOMPosition pos = basePosition().equivalentLeafPosition();
+ m_baseNode = pos.node();
+ m_baseOffset = pos.offset();
+ if (baseAndExtentEqual) {
+ m_extentNode = pos.node();
+ m_extentOffset = pos.offset();
+ }
+ }
+ if (m_extentNode && !baseAndExtentEqual) {
+ DOMPosition pos = extentPosition().equivalentLeafPosition();
+ m_extentNode = pos.node();
+ m_extentOffset = pos.offset();
+ }
+
// make sure we do not have a dangling start or end
if (!m_baseNode && !m_extentNode) {
setBaseOffset(0);
fprintf(stderr, "downstream: %s %p:%d\n", getTagName(downstream.node()->id()).string().latin1(), downstream.node(), downstream.offset());
}
else {
- DOMPosition pos = endPosition();
+ DOMPosition pos = startPosition();
DOMPosition upstream = pos.equivalentUpstreamPosition();
DOMPosition downstream = pos.equivalentDownstreamPosition();
fprintf(stderr, "upstream: %s %p:%d\n", getTagName(upstream.node()->id()).string().latin1(), upstream.node(), upstream.offset());
fprintf(stderr, "start: %s %p:%d\n", getTagName(pos.node()->id()).string().latin1(), pos.node(), pos.offset());
fprintf(stderr, "downstream: %s %p:%d\n", getTagName(downstream.node()->id()).string().latin1(), downstream.node(), downstream.offset());
fprintf(stderr, "-----------------------------------\n");
- pos = startPosition();
+ pos = endPosition();
upstream = pos.equivalentUpstreamPosition();
downstream = pos.equivalentDownstreamPosition();
fprintf(stderr, "upstream: %s %p:%d\n", getTagName(upstream.node()->id()).string().latin1(), upstream.node(), upstream.offset());
return nonBreakingSpaceString;
}
+static void debugPosition(const char *prefix, const DOMPosition &pos)
+{
+ LOG(Editing, "%s%s %p : %d", prefix, getTagName(pos.node()->id()).string().latin1(), pos.node(), pos.offset());
+}
+
//------------------------------------------------------------------------------------------
// EditCommandImpl
unsigned long count = it.current().offset() - deleteStart.offset();
if (count == textNode->length()) {
LOG(Editing, " removeNodeAndPrune 1: [%p]\n", textNode);
+ if (textNode == endingPosition.node())
+ endingPosition = DOMPosition(next.node(), next.node()->caretMinOffset());
removeNodeAndPrune(textNode);
}
else {
}
}
-static void debugPosition(const char *prefix, const DOMPosition &pos)
-{
- LOG(Editing, "%s%s %p : %d", prefix, getTagName(pos.node()->id()).string().latin1(), pos.node(), pos.offset());
-}
-
-enum { NoPositionModification, MoveDownstreamPositionModification, MoveToNextCharacterModification };
-
void DeleteSelectionCommandImpl::doApply()
{
if (m_selectionToDelete.state() != KHTMLSelection::RANGE)
// Start is not completely selected
if (startAtStartOfBlock) {
LOG(Editing, "ending position case 1");
- endingPosition = DOMPosition(downstreamStart.node()->containingEditableBlock(), 1);
+ endingPosition = DOMPosition(downstreamStart.node()->containingEditableBlock(), 0);
adjustEndingPositionDownstream = true;
}
else if (!startCompletelySelected) {
ASSERT(selection.state() == KHTMLSelection::CARET);
DOMPosition pos = selection.startPosition().equivalentUpstreamPosition();
+ if (!pos.inRenderedContent())
+ pos = pos.nextRenderedEditablePosition();
if (!pos.node()->inSameContainingEditableBlock(selection.startNode()))
pos = selection.startPosition();
if (pos.node()->isEditableBlock())
appendNode(pos.node(), m_insertedTextNode);
- else if (pos.node()->id() == ID_BR || pos.offset() == 1)
- insertNodeAfter(m_insertedTextNode, pos.node());
- else {
- ASSERT(pos.offset() == 0);
+ else if (pos.node()->id() == ID_BR && pos.offset() == 1)
insertNodeBefore(m_insertedTextNode, pos.node());
- }
+ else if (pos.node()->caretMinOffset() == pos.offset())
+ insertNodeBefore(m_insertedTextNode, pos.node());
+ else if (pos.node()->caretMaxOffset() == pos.offset())
+ insertNodeAfter(m_insertedTextNode, pos.node());
+ else
+ ASSERT_NOT_REACHED();
pos = DOMPosition(m_insertedTextNode, 0);
}
KHTMLSelection selection = currentSelection();
// Delete the current selection
- deleteSelection();
+ if (selection.state() == KHTMLSelection::RANGE)
+ deleteSelection();
+ else
+ deleteCollapsibleWhitespace();
// Make sure the document is set up to receive text
DOMPosition pos = prepareForTextInsertion();
if (selection.state() == KHTMLSelection::CARET) {
KHTMLSelection selectionToDelete(selection.startPosition().previousCharacterPosition(), selection.startPosition());
- setEndingSelection(selectionToDelete);
- deleteCollapsibleWhitespace();
- selection = currentSelection();
- deleteSelection(selection);
+ deleteCollapsibleWhitespace(selectionToDelete);
}
else { // selection.state() == KHTMLSelection::RANGE
deleteCollapsibleWhitespace();
- deleteSelection();
}
+ deleteSelection(endingSelection());
}
void TypingCommandImpl::deleteKeyPressed()
void KHTMLSelection::validate(ETextGranularity expandTo)
{
+ // move the base and extent nodes to their equivalent leaf positions
+ bool baseAndExtentEqual = m_baseNode == m_extentNode && m_baseOffset == m_extentOffset;
+ if (m_baseNode) {
+ DOMPosition pos = basePosition().equivalentLeafPosition();
+ m_baseNode = pos.node();
+ m_baseOffset = pos.offset();
+ if (baseAndExtentEqual) {
+ m_extentNode = pos.node();
+ m_extentOffset = pos.offset();
+ }
+ }
+ if (m_extentNode && !baseAndExtentEqual) {
+ DOMPosition pos = extentPosition().equivalentLeafPosition();
+ m_extentNode = pos.node();
+ m_extentOffset = pos.offset();
+ }
+
// make sure we do not have a dangling start or end
if (!m_baseNode && !m_extentNode) {
setBaseOffset(0);
fprintf(stderr, "downstream: %s %p:%d\n", getTagName(downstream.node()->id()).string().latin1(), downstream.node(), downstream.offset());
}
else {
- DOMPosition pos = endPosition();
+ DOMPosition pos = startPosition();
DOMPosition upstream = pos.equivalentUpstreamPosition();
DOMPosition downstream = pos.equivalentDownstreamPosition();
fprintf(stderr, "upstream: %s %p:%d\n", getTagName(upstream.node()->id()).string().latin1(), upstream.node(), upstream.offset());
fprintf(stderr, "start: %s %p:%d\n", getTagName(pos.node()->id()).string().latin1(), pos.node(), pos.offset());
fprintf(stderr, "downstream: %s %p:%d\n", getTagName(downstream.node()->id()).string().latin1(), downstream.node(), downstream.offset());
fprintf(stderr, "-----------------------------------\n");
- pos = startPosition();
+ pos = endPosition();
upstream = pos.equivalentUpstreamPosition();
downstream = pos.equivalentDownstreamPosition();
fprintf(stderr, "upstream: %s %p:%d\n", getTagName(upstream.node()->id()).string().latin1(), upstream.node(), upstream.offset());
};
namespace KJS {
+ class Selection;
+ class SelectionFunc;
class Window;
class WindowFunc;
class JSEventListener;
friend class KHTMLRun;
friend class DOM::HTMLFormElementImpl;
friend class khtml::RenderPartObject;
+ friend class KJS::Selection;
+ friend class KJS::SelectionFunc;
friend class KJS::Window;
friend class KJS::WindowFunc;
friend class KJS::JSEventListener;
void KHTMLSelection::validate(ETextGranularity expandTo)
{
+ // move the base and extent nodes to their equivalent leaf positions
+ bool baseAndExtentEqual = m_baseNode == m_extentNode && m_baseOffset == m_extentOffset;
+ if (m_baseNode) {
+ DOMPosition pos = basePosition().equivalentLeafPosition();
+ m_baseNode = pos.node();
+ m_baseOffset = pos.offset();
+ if (baseAndExtentEqual) {
+ m_extentNode = pos.node();
+ m_extentOffset = pos.offset();
+ }
+ }
+ if (m_extentNode && !baseAndExtentEqual) {
+ DOMPosition pos = extentPosition().equivalentLeafPosition();
+ m_extentNode = pos.node();
+ m_extentOffset = pos.offset();
+ }
+
// make sure we do not have a dangling start or end
if (!m_baseNode && !m_extentNode) {
setBaseOffset(0);
fprintf(stderr, "downstream: %s %p:%d\n", getTagName(downstream.node()->id()).string().latin1(), downstream.node(), downstream.offset());
}
else {
- DOMPosition pos = endPosition();
+ DOMPosition pos = startPosition();
DOMPosition upstream = pos.equivalentUpstreamPosition();
DOMPosition downstream = pos.equivalentDownstreamPosition();
fprintf(stderr, "upstream: %s %p:%d\n", getTagName(upstream.node()->id()).string().latin1(), upstream.node(), upstream.offset());
fprintf(stderr, "start: %s %p:%d\n", getTagName(pos.node()->id()).string().latin1(), pos.node(), pos.offset());
fprintf(stderr, "downstream: %s %p:%d\n", getTagName(downstream.node()->id()).string().latin1(), downstream.node(), downstream.offset());
fprintf(stderr, "-----------------------------------\n");
- pos = startPosition();
+ pos = endPosition();
upstream = pos.equivalentUpstreamPosition();
downstream = pos.equivalentDownstreamPosition();
fprintf(stderr, "upstream: %s %p:%d\n", getTagName(upstream.node()->id()).string().latin1(), upstream.node(), upstream.offset());
#endif // APPLE_CHANGES
+bool DocumentImpl::execCommand(const DOMString &command, bool userInterface, const DOMString &value)
+{
+ static AtomicString selectAllCommand("selectall");
+ static AtomicString insertTextCommand("inserttext");
+ static AtomicString undoCommand("undo");
+ static AtomicString redoCommand("redo");
+ static AtomicString deleteCommand("delete");
+
+ updateLayout();
+
+ AtomicString atom(command.lower());
+ if (atom == selectAllCommand) {
+ if (!part())
+ return false;
+ part()->selectAll();
+ return true;
+ }
+ else if (atom == insertTextCommand) {
+ if (!part() || part()->selection().isEmpty())
+ return false;
+ TypingCommand::insertText(this, value);
+ return true;
+ }
+ else if (atom == undoCommand) {
+ if (!part())
+ return false;
+ KWQ(part())->issueUndoCommand();
+ return true;
+ }
+ else if (atom == redoCommand) {
+ if (!part())
+ return false;
+ KWQ(part())->issueRedoCommand();
+ return true;
+ }
+ else if (atom == deleteCommand) {
+ if (!part() || part()->selection().isEmpty())
+ return false;
+ TypingCommand::deleteKeyPressed(this);
+ return true;
+ }
+
+ return false;
+}
+
// ----------------------------------------------------------------------------
DocumentFragmentImpl::DocumentFragmentImpl(DocumentPtr *doc) : NodeBaseImpl(doc)
DOMString toString() const;
+ bool execCommand(const DOMString &command, bool userInterface, const DOMString &value);
+
#ifndef KHTML_NO_XBL
// XBL methods
XBL::XBLBindingManager* bindingManager() const { return m_bindingManager; }
while (node) {
if (!node->isContentEditable())
return 0;
- if (node->hasChildNodes() == 0)
+ if (node->hasChildNodes() == false)
return node;
node = node->traversePreviousNode();
}
while (node) {
if (!node->isContentEditable())
return 0;
- if (node->hasChildNodes() == 0)
+ if (node->hasChildNodes() == false)
return node;
node = node->traverseNextNode();
}
return result;
}
+DOMPosition DOMPosition::equivalentLeafPosition() const
+{
+ if (node()->hasChildNodes() == false)
+ return *this;
+
+ NodeImpl *n = node();
+ int count = 0;
+ while (1) {
+ n = n->nextLeafNode();
+ if (!n)
+ return *this;
+ if (count + n->maxOffset() >= offset()) {
+ count = offset() - count;
+ break;
+ }
+ count += n->maxOffset();
+ }
+ return DOMPosition(n, count);
+}
+
+DOMPosition DOMPosition::previousRenderedEditablePosition() const
+{
+ if (isEmpty())
+ return DOMPosition();
+
+ if (node()->isContentEditable() && node()->hasChildNodes() == false && inRenderedContent())
+ return *this;
+
+ NodeImpl *n = node();
+ while (1) {
+ n = n->previousEditable();
+ if (!n)
+ return DOMPosition();
+ if (n->renderer() && n->renderer()->style()->visibility() == khtml::VISIBLE)
+ break;
+ }
+
+ return DOMPosition(n, 0);
+}
+
+DOMPosition DOMPosition::nextRenderedEditablePosition() const
+{
+ if (isEmpty())
+ return DOMPosition();
+
+ if (node()->isContentEditable() && node()->hasChildNodes() == false && inRenderedContent())
+ return *this;
+
+ NodeImpl *n = node();
+ while (1) {
+ n = n->nextEditable();
+ if (!n)
+ return DOMPosition();
+ if (n->renderer() && n->renderer()->style()->visibility() == khtml::VISIBLE)
+ break;
+ }
+
+ return DOMPosition(n, 0);
+}
+
DOMPosition DOMPosition::previousCharacterPosition() const
{
if (isEmpty())
if (!node())
return DOMPosition();
- if (!node()->isTextNode() && offset() > node()->caretMinOffset())
- return *this;
-
NodeImpl *block = node()->containingEditableBlock();
-
- EditIterator it(*this);
- DOMPosition prev = it.peekPrevious();
- if (validUpstreamDownstreamPosition() && prev.validUpstreamDownstreamPosition()) {
- if (node() == prev.node())
- return *this;
- else
- return prev;
- }
- while (!it.atStart()) {
- it.previous();
- if (it.current().validUpstreamDownstreamPosition())
- return it.current();
+
+ EditIterator it(*this);
+ for (; !it.atStart(); it.previous()) {
if (block != it.current().node()->containingEditableBlock())
return it.next();
+
+ if (!node()->isContentEditable())
+ return it.next();
+
+ RenderObject *renderer = it.current().node()->renderer();
+ if (!renderer)
+ continue;
+
+ if (renderer->style()->visibility() != khtml::VISIBLE)
+ continue;
+
+ if (renderer->isBlockFlow() || renderer->isReplaced() || renderer->isBR()) {
+ if (it.current().offset() >= renderer->caretMaxOffset())
+ return DOMPosition(it.current().node(), renderer->caretMaxOffset());
+ else
+ continue;
+ }
+
+ if (renderer->isText() && static_cast<RenderText *>(renderer)->firstTextBox()) {
+ if (it.current().node() != node())
+ return DOMPosition(it.current().node(), renderer->caretMaxOffset());
+
+ if (it.current().offset() < 0)
+ continue;
+ uint textOffset = it.current().offset();
+
+ RenderText *textRenderer = static_cast<RenderText *>(renderer);
+ for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
+ if (textOffset > box->start() && textOffset <= box->start() + box->len())
+ return it.current();
+ }
+ }
}
- return *this;
+
+ return it.current();
}
DOMPosition DOMPosition::equivalentDownstreamPosition() const
if (!node())
return DOMPosition();
- if (!node()->isTextNode() && offset() < node()->caretMaxOffset())
- return *this;
-
NodeImpl *block = node()->containingEditableBlock();
-
- EditIterator it(*this);
- DOMPosition next = it.peekNext();
- if (validUpstreamDownstreamPosition() && next.validUpstreamDownstreamPosition()) {
- if (node() == next.node())
- return *this;
- else
- return next;
- }
- while (!it.atEnd()) {
- if (it.next().validUpstreamDownstreamPosition())
- return it.current();
+
+ EditIterator it(*this);
+ for (; !it.atEnd(); it.next()) {
if (block != it.current().node()->containingEditableBlock())
return it.previous();
- }
- return *this;
-}
-bool DOMPosition::validUpstreamDownstreamPosition() const
-{
- if (isEmpty())
- return false;
-
- RenderObject *renderer = node()->renderer();
- if (!renderer || !renderer->isEditable())
- return false;
+ if (!node()->isContentEditable())
+ return it.next();
+
+ RenderObject *renderer = it.current().node()->renderer();
+ if (!renderer)
+ continue;
- if (renderer->style()->visibility() != khtml::VISIBLE)
- return false;
+ if (renderer->style()->visibility() != khtml::VISIBLE)
+ continue;
- if (renderer->isBR() || renderer->isBlockFlow())
- return true;
-
- if (renderer->isText()) {
- RenderText *textRenderer = static_cast<RenderText *>(renderer);
- InlineTextBox *lastTextBox = textRenderer->lastTextBox();
- for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
- if (offset() >= box->m_start) {
- if (box == lastTextBox) {
- if (offset() <= box->m_start + box->m_len)
- return true;
- }
- else if (offset() < box->m_start + box->m_len)
- return true;
- }
- else if (offset() < box->m_start) {
- // The offset we're looking for is before this node
- // this means the offset must be in content that is
- // not rendered. Return false.
- return false;
+ if (renderer->isBlockFlow() || renderer->isReplaced() || renderer->isBR()) {
+ if (it.current().offset() <= renderer->caretMinOffset())
+ return DOMPosition(it.current().node(), renderer->caretMinOffset());
+ else
+ continue;
+ }
+
+ if (renderer->isText() && static_cast<RenderText *>(renderer)->firstTextBox()) {
+ if (it.current().node() != node())
+ return DOMPosition(it.current().node(), renderer->caretMinOffset());
+
+ if (it.current().offset() < 0)
+ continue;
+ uint textOffset = it.current().offset();
+
+ RenderText *textRenderer = static_cast<RenderText *>(renderer);
+ for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
+ if (textOffset >= box->start() && textOffset <= box->end())
+ return it.current();
}
}
- return false;
}
- if (offset() >= renderer->caretMinOffset() && offset() <= renderer->caretMaxOffset())
- return true;
-
- return false;
+ return it.current();
+}
+
+bool DOMPosition::atStartOfContainingEditableBlock() const
+{
+ return renderedOffset() == 0 && inFirstEditableInContainingEditableBlock();
+}
+
+bool DOMPosition::atStartOfRootEditableBlock() const
+{
+ return renderedOffset() == 0 && inFirstEditableInRootEditableBlock();
}
bool DOMPosition::inRenderedContent() const
bool isEmpty() const { return m_node == 0; }
bool notEmpty() const { return m_node != 0; }
+ DOMPosition equivalentLeafPosition() const;
+ DOMPosition previousRenderedEditablePosition() const;
+ DOMPosition nextRenderedEditablePosition() const;
DOMPosition previousCharacterPosition() const;
DOMPosition nextCharacterPosition() const;
DOMPosition equivalentUpstreamPosition() const;
DOMPosition equivalentDownstreamPosition() const;
- bool validUpstreamDownstreamPosition() const;
+ bool atStartOfContainingEditableBlock() const;
+ bool atStartOfRootEditableBlock() const;
bool inRenderedContent() const;
bool inRenderedText() const;
bool rendersOnSameLine(const DOMPosition &pos) const;
void KHTMLSelection::validate(ETextGranularity expandTo)
{
+ // move the base and extent nodes to their equivalent leaf positions
+ bool baseAndExtentEqual = m_baseNode == m_extentNode && m_baseOffset == m_extentOffset;
+ if (m_baseNode) {
+ DOMPosition pos = basePosition().equivalentLeafPosition();
+ m_baseNode = pos.node();
+ m_baseOffset = pos.offset();
+ if (baseAndExtentEqual) {
+ m_extentNode = pos.node();
+ m_extentOffset = pos.offset();
+ }
+ }
+ if (m_extentNode && !baseAndExtentEqual) {
+ DOMPosition pos = extentPosition().equivalentLeafPosition();
+ m_extentNode = pos.node();
+ m_extentOffset = pos.offset();
+ }
+
// make sure we do not have a dangling start or end
if (!m_baseNode && !m_extentNode) {
setBaseOffset(0);
fprintf(stderr, "downstream: %s %p:%d\n", getTagName(downstream.node()->id()).string().latin1(), downstream.node(), downstream.offset());
}
else {
- DOMPosition pos = endPosition();
+ DOMPosition pos = startPosition();
DOMPosition upstream = pos.equivalentUpstreamPosition();
DOMPosition downstream = pos.equivalentDownstreamPosition();
fprintf(stderr, "upstream: %s %p:%d\n", getTagName(upstream.node()->id()).string().latin1(), upstream.node(), upstream.offset());
fprintf(stderr, "start: %s %p:%d\n", getTagName(pos.node()->id()).string().latin1(), pos.node(), pos.offset());
fprintf(stderr, "downstream: %s %p:%d\n", getTagName(downstream.node()->id()).string().latin1(), downstream.node(), downstream.offset());
fprintf(stderr, "-----------------------------------\n");
- pos = startPosition();
+ pos = endPosition();
upstream = pos.equivalentUpstreamPosition();
downstream = pos.equivalentDownstreamPosition();
fprintf(stderr, "upstream: %s %p:%d\n", getTagName(upstream.node()->id()).string().latin1(), upstream.node(), upstream.offset());
void registerCommandForRedo(const khtml::EditCommand &);
void clearUndoRedoOperations();
void editingKeyEvent();
+ void issueUndoCommand();
+ void issueRedoCommand();
private:
virtual void khtmlMousePressEvent(khtml::MousePressEvent *);
[_bridge editingKeyDown:_currentEvent];
}
+void KWQKHTMLPart::issueUndoCommand()
+{
+ [_bridge issueUndoCommand];
+}
+
+void KWQKHTMLPart::issueRedoCommand()
+{
+ [_bridge issueRedoCommand];
+}
#include "htmltags.h"
#include "khtmlview.h"
+#include "khtml_selection.h"
#include "render_replaced.h"
#include "render_table.h"
#include "render_text.h"
#include "render_canvas.h"
+#include "xml/dom_docimpl.h"
+#include "xml/dom_nodeimpl.h"
+#include "xml/dom_position.h"
#include "KWQKHTMLPart.h"
#include "KWQTextStream.h"
+using DOM::DocumentImpl;
+using DOM::DOMPosition;
+using DOM::NodeImpl;
using khtml::RenderLayer;
using khtml::RenderObject;
using khtml::RenderTableCell;
}
}
+static QString nodePositionRelativeToRoot(NodeImpl *node, NodeImpl *root)
+{
+ QString result;
+
+ NodeImpl *n = node;
+ while (1) {
+ NodeImpl *p = n->parentNode();
+ if (!p || n == root) {
+ result += " of root {" + getTagName(n->id()).string() + "}";
+ break;
+ }
+ if (n != node)
+ result += " of ";
+ int count = 1;
+ for (NodeImpl *search = p->firstChild(); search != n; search = search->nextSibling())
+ count++;
+ result += "child " + QString::number(count) + " {" + getTagName(n->id()).string() + "}";
+ n = p;
+ }
+
+ return result;
+}
+
+static void writeSelection(QTextStream &ts, const RenderObject *o)
+{
+ DocumentImpl *doc = dynamic_cast<DocumentImpl *>(o->element());
+ if (!doc || !doc->part())
+ return;
+
+ KHTMLSelection selection = doc->part()->selection();
+ if (selection.state() == KHTMLSelection::NONE)
+ return;
+
+ if (!selection.startPosition().node()->isContentEditable() || !selection.endPosition().node()->isContentEditable())
+ return;
+
+ DOMPosition startPosition = selection.startPosition();
+ DOMPosition endPosition = selection.endPosition();
+
+ QString startNodeTagName(getTagName(startPosition.node()->id()).string());
+ QString endNodeTagName(getTagName(endPosition.node()->id()).string());
+
+ NodeImpl *rootNode = doc->getElementById("root");
+
+ if (selection.state() == KHTMLSelection::CARET) {
+ DOMPosition upstream = startPosition.equivalentUpstreamPosition();
+ DOMPosition downstream = startPosition.equivalentDownstreamPosition();
+ QString positionString = nodePositionRelativeToRoot(startPosition.node(), rootNode);
+ QString upstreamString = nodePositionRelativeToRoot(upstream.node(), rootNode);
+ QString downstreamString = nodePositionRelativeToRoot(downstream.node(), rootNode);
+ ts << "selection is CARET:\n" <<
+ "start: position " << startPosition.offset() << " of " << positionString << "\n"
+ "upstream: position " << upstream.offset() << " of " << upstreamString << "\n"
+ "downstream: position " << downstream.offset() << " of " << downstreamString << "\n";
+ }
+ else if (selection.state() == KHTMLSelection::RANGE) {
+ QString startString = nodePositionRelativeToRoot(startPosition.node(), rootNode);
+ DOMPosition upstreamStart = startPosition.equivalentUpstreamPosition();
+ QString upstreamStartString = nodePositionRelativeToRoot(upstreamStart.node(), rootNode);
+ DOMPosition downstreamStart = startPosition.equivalentDownstreamPosition();
+ QString downstreamStartString = nodePositionRelativeToRoot(downstreamStart.node(), rootNode);
+ QString endString = nodePositionRelativeToRoot(endPosition.node(), rootNode);
+ DOMPosition upstreamEnd = endPosition.equivalentUpstreamPosition();
+ QString upstreamEndString = nodePositionRelativeToRoot(upstreamEnd.node(), rootNode);
+ DOMPosition downstreamEnd = endPosition.equivalentDownstreamPosition();
+ QString downstreamEndString = nodePositionRelativeToRoot(downstreamEnd.node(), rootNode);
+ ts << "selection is RANGE:\n" <<
+ "start: position " << startPosition.offset() << " of " << startString << "\n" <<
+ "upstream: position " << upstreamStart.offset() << " of " << upstreamStartString << "\n"
+ "downstream: position " << downstreamStart.offset() << " of " << downstreamStartString << "\n"
+ "end: position " << endPosition.offset() << " of " << endString << "\n"
+ "upstream: position " << upstreamEnd.offset() << " of " << upstreamEndString << "\n"
+ "downstream: position " << downstreamEnd.offset() << " of " << downstreamEndString << "\n";
+ }
+}
+
QString externalRepresentation(RenderObject *o)
{
QString s;
o->canvas()->view()->setVScrollBarMode(QScrollView::AlwaysOff);
o->canvas()->view()->layout();
RenderLayer* l = o->layer();
- if (l)
+ if (l) {
writeLayers(ts, l, l, QRect(l->xPos(), l->yPos(), l->width(), l->height()));
+ writeSelection(ts, o);
+ }
}
}
return s;
- (void)registerCommandForUndo:(id)arg;
- (void)registerCommandForRedo:(id)arg;
- (void)clearUndoRedoOperations;
+- (void)issueUndoCommand;
+- (void)issueRedoCommand;
- (void)editingKeyDown:(NSEvent *)event;
+2004-04-06 Ken Kocienda <kocienda@apple.com>
+
+ Reviewed by Dave
+
+ * WebCoreSupport.subproj/WebBridge.m:
+ (-[WebBridge issueUndoCommand]): New method. Forwards call to the undo manager. Added
+ to support undo called via Javascript execCommand.
+ (-[WebBridge issueRedoCommand]): Ditto.
+
2004-04-05 Chris Blumenberg <cblu@apple.com>
Fixed: <rdar://problem/3612580>: SPI: WebPlugin selection
[[_frame webView] editingKeyDown:event];
}
+- (void)issueUndoCommand
+{
+ NSUndoManager *undoManager = [[_frame webView] undoManager];
+ if ([undoManager canUndo])
+ [undoManager undo];
+}
+
+- (void)issueRedoCommand
+{
+ NSUndoManager *undoManager = [[_frame webView] undoManager];
+ if ([undoManager canRedo])
+ [undoManager redo];
+}
+
- (void)setIsSelected:(BOOL)isSelected forView:(NSView *)view
{
if ([view conformsToProtocol:@protocol(WebPluginSelection)]) {