WebKit Undo实现机制

   WebKit Undo是指用户在网页编辑状态下,点击'ctrl+z'后,实现页面编辑状态的回滚,依赖于WebKit Editor的实现; 

    WebKit Editor的工作机制采用了类似于命令模式的实现方案,将一系列编辑操作抽象成各种Command实现;这些Command继承自公共的基类EditCommand, Command之间的关系呈树型结构,类图如下所示:

         

    EditCommand是所有编辑命令的抽象基类,编辑操作最终通过调用EditCommand::doApply()来实现; 

    CompositeEditCommand提供了实现具体编辑操作的接口,也作为其他编辑命令的基类;其中Vector<RefPtr<EditCommand>>类型的成员变量m_commands,保存了编辑操作的子命令; RefPtr<EditCommandComposition>类型的成员变量m_composition保存了同一个编辑状态下的所有SimpleEditCommand, 用于实现后续undo操作;  

    UndoStep是一个抽象基类,提供了实现redo/undo的两个接口reapply()和unapply();

    EditCommandComposition重写了基类方法unapply()和reapply();在用户发起redo/undo操作后,将会遍历成员变量m_commands保存的所有SimpleEditCommand,  逐个执行doReapply()/doUnapply();

    TypingCommand提供了一系列的静态方法,Editor对象通过这些静态方法完成编辑功能; TypingCommand提供了简单编辑接口,具体的编辑操作通过CompositeEditCommand中的各个接口完成。

      先来了解Editor插入文本的流程.     

      1. Editor的编辑操作,调用静态方法TypingCommand::insertText()实现;该函数首先会判定lastTypingCommandIfStillOpenForTyping()是否为true,简单说来,就是输入法是否打开,并且Editor::m_lastEditCommand是否存在; 如果存在,表明当前的输入文本操作是同一个编辑操作的延续,也就是处于同一个undo的状态下; 如果不存在,需要创建新的TypingCommand对象; 该对象在完成编辑操作后,在Editor::appliedEditing()中,将被设置为Editor::m_lastEditCommand;

      2. 在TypingCommand::insertTextRunWithoutNewlines()中,创建InsertTextCommand对象,实现插入文本具体操作;

     3. InsertTextCommand::doApply()函数调用CompositeEditCommand::insertTextIntoNode()方法,创建InsertIntoTextNodeCommand对象,该对象最终将文本插入到关联的文本结点上;

     该流程的调用栈如下:

WebCore::EditCommandComposition::append(..)
WebCore::CompositeEditCommand::applyCommandToComposite(..)
WebCore::CompositeEditCommand::insertTextIntoNode(..)
WebCore::InsertTextCommand::doApply(..)
WebCore::CompositeEditCommand::applyCommandToComposite(..)
WebCore::TypingCommand::insertTextRunWithoutNewlines(..)
WebCore::TypingCommandLineOperation::operator(..) const
void WebCore::forEachLineInString<WebCore::TypingCommandLineOperation>(..)
WebCore::TypingCommand::insertText(..)
WebCore::TypingCommand::doApply(..)
WebCore::CompositeEditCommand::apply(..)
WebCore::applyCommand(..)
WebCore::TextInsertionBaseCommand::applyTextInsertionCommand(..)
WebCore::TypingCommand::insertText(..)

Undo的实现可以分成以下几个步骤:

    1.最终的编辑操作执行doApply()以后,将被添加到Editor::m_lastEditCommand对应EditCommmandComposition集合中; 函数CompositeEditCommand::applyCommandToComposite()首先会找到当前编辑命令的根命令; 例如,对于插入操作,当前的编辑命令是InsertTextCommand, 而其对应的根命令刚好是CompositeEditCommand类型的Editor::m_lastEditCommand;找到该根命令后,再将InsertTextCommnd命令添加到Editor::m_lastEditCommand的成员变量m_composition中,作为整个undo操作的一个子操作, 调用栈如下:

WebCore::EditCommandComposition::append(..)
WebCore::CompositeEditCommand::applyCommandToComposite(..)
WebCore::CompositeEditCommand::insertTextIntoNode(..)
WebCore::InsertTextCommand::doApply(..)
WebCore::CompositeEditCommand::applyCommandToComposite(..)
WebCore::TypingCommand::insertTextRunWithoutNewlines(..)
WebCore::TypingCommandLineOperation::operator(..) const
void WebCore::forEachLineInString<WebCore::TypingCommandLineOperation>(..)
WebCore::TypingCommand::insertText(..)
WebCore::TypingCommand::doApply(..)
WebCore::CompositeEditCommand::apply(..)
WebCore::applyCommand(..)
WebCore::TextInsertionBaseCommand::applyTextInsertionCommand(..)
WebCore::TypingCommand::insertText(..)

     2. 如果是首次进行编辑操作,即Editor::m_lastEditCommand首次执行,需要在WebEditorClient::registerUndoStep()中注册;该函数首先创建一个全局的stepID,以stepID和 Editor::m_lastEditCommand::m_composition构建WebUndoStep对象,并添加到WebPage::m_undoStepMap中,该HashMap即以stepID为key; 最后WebUndoStep对象的信息被发送到外壳进行保存;stepID在外壳保存机制没有深入研究, 应该是采用了类似iOS NSUndoManager的管理方式,该类有两个成员变量_undoStack和_redoStack用于保存WebUndoStep的简单信息(stepID),有待确认;调用栈如下:

WebKit::WebEditorClient::registerUndoStep(..)
WebCore::Editor::appliedEditing(..)
WebCore::TypingCommand::typingAddedToOpenCommand(..)
WebCore::TypingCommand::insertTextRunWithoutNewlines(..)
WebCore::TypingCommandLineOperation::operator(..)
WebCore::forEachLineInString<WebCore::TypingCommandLineOperation>(..)
WebCore::TypingCommand::insertText(..)
WebCore::TypingCommand::doApply(..)
WebCore::CompositeEditCommand::apply(..)
          其中注册相关的类如下图

     

      3. 需要注意的是,如果不是首次进行编辑操作,不会注册WebUndoStep;内核仅将该编辑命令添加到Editor::m_lastEditCommand::m_composition中作为一个子操作,也就是第1步中实现的流程;

     4. 用户进行undo操作,即按下’ctrl+z ‘(对于ios键盘,切换到数字键后就有’undo’键)后,外壳弹出undoStack中保存的stepID,  告诉内核将stepID对应的操作实现undo;  WebKit内核在收到该消息后,首先从 WebPage::m_undoStepMap中取出stepID对应的WebUndoStep项,再通过成员变量m_step指向的 EditCommandComposition对象执行unapply()操作;因为EditCommandComposition对象保存了同一个undo场景下的所有SimpleEditCommand类型的子命令,因此unapply()函数的实现就是遍历执行子命令的doUnapply()方法; 以下是EditCommandComposition::unapply()的部分代码:

size_t size = m_commands.size();
        for (size_t i = size; i; --i)
            m_commands[i - 1]->doUnapply();
        用户进行undo操作的调用栈如下:

WebCore::InsertIntoTextNodeCommand::doUnapply() 
WebCore::EditCommandComposition::unapply() 
WebKit::WebPage::unapplyEditCommand(unsigned long long)

       从上面的研究可以看出,一次undo操作对应一个stepID, 而stepID又与Editor::m_lastEditCommand关联, 因此m_lastEditCommand的生命周期是维护undo操作状态的关键;我们已经看到m_lastEditCommand是首次编辑的时候创建的,创建的条件依赖于 lastTypingCommandIfStillOpenForTyping()函数的返回值;该函数检测到Editor::m_lastEditCommand存在,并且Editor::m_lastEditCommand::m_openForMoreTyping为true时,返回最终结果true;  Editor::m_lastEditCommand 在执行undo后被置为0,而m_openForMoreTyping则在TypingCommand::closeTying()中置为false, 比如用户点击输入框以外的区域,输入框失去焦点后关闭输入法; 因此用户只要没有关闭输入法或者执行undo操作,那么所有的编辑命令都属于同一个编辑状态, 也就是同一个Undo场景下的子操作; 否则进入下一个编辑状态。

    比如用户在输入框中依次输入’a’, ‘b’,’c’,’d’, 因为这4次输入都属于同一个编辑状态,因此undo操作的结果是回滚到输入’a’之前的状态; 如果用户输入’b’以后, 关闭输入法, 再输入’c’, ‘d’,此时进行undo操作则只能回滚到’ab’, 再次undo才能回滚到输入’a’之前的状态;

         Redo和Undo操作的实现流程类似,这里就不再赘述了。

    

         







    


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值