前面已经介绍了这么引人的输入自动完成功能,并且可以在输入超级连接框里直接通过GOOGLE搜索所有的内容,这是比较大的创新,不但可以节省界面的占用面积,还很方便大家查询的需要,比如记不住的连接,根本不需要去记了,只要你记住需要的内容就行了。这样既不需要到什么门户网站去找连接,也不需要去记住众多的网站,这个功能是非常方便的。

这个输入框的自动完成的功能,是比较智能化的。因为它会根据以往的输入自动完成,或者智能提示所需要的连接或者内容。

下面就来先看这个类的定义:

#001  // Provides the implementation of an edit control with a drop-down

#002  // autocomplete box. The box itself is implemented in autocomplete_popup.cc

#003  // This file implements the edit box and management for the popup.

#004  //

#005  // This implementation is currently appropriate for the URL bar, where the

#006  // autocomplete dropdown is always displayed because there is always a

#007  // default item. For web page autofill and other applications, this is

#008  // probably not appropriate. We may want to add a flag to determine which

#009  // of these modes we're in.

#010  class AutocompleteEdit

#011      : public CWindowImpl<AutocompleteEdit,

#012                           CRichEditCtrl,

#013                           CWinTraits<WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL |

#014                                      ES_NOHIDESEL> >,

#015        public CRichEditCommands<AutocompleteEdit>,

#016        public Menu::Delegate {

类AutocompleteEdit继承了类CWindowImpl、类CRichEditCommands、类Menu::Delegate。其中类CWindowImpl实现了Windows窗口,它是WTL里的窗口模板类,主要用来创建窗口界面类,并且使用类CRichEditCtrl作为基类,类CRichEditCtrl主要调用Windows里的编辑类。类CRichEditCommands实现RichEdit的命令功能。Menu::Delegate类是实现智能下拉式菜单的提示界面。因此,要学习开发chrome,需要先学习WTL的开发,它是一套基于模板的窗口框架。下一次再仔细地分析自动完成的实现过程。

当我们键入字母或者文字开始时,那么类AutocompleteEdit就会从窗口消息里获取到相应的字母或者文字,然后根据输入的信息到本地或者网络上保存的信息库里查找相应的输入提示,这就是自动完成的实现。下面就来先分析输入的函数:

#001  void AutocompleteEdit::OnChar(TCHAR ch, UINT repeat_count, UINT flags) {

#002    // Don't let alt-enter beep.  Not sure this is necessary, as the standard

#003    // alt-enter will hit DiscardWMSysChar() and get thrown away, and

#004    // ctrl-alt-enter doesn't seem to reach here for some reason?  At least not on

#005    // my system... still, this is harmless and maybe necessary in other locales.

下面把alt-enter组合键消息过滤掉。

#006    if (ch == VK_RETURN && (flags & KF_ALTDOWN))

#007      return;

#008

#009    // Escape is processed in OnKeyDown.  Don't let any WM_CHAR messages propagate

#010    // as we don't want the RichEdit to do anything funky.

下面把ESC键的消息过滤掉。

#011    if (ch == VK_ESCAPE && !(flags & KF_ALTDOWN))

#012      return;

#013

下面把TAB键的消息过滤掉。

#014    if (ch == VK_TAB) {

#015      // Don't add tabs to the input.

#016      return;

#017    }

#018

这里处理其它有用的按键消息。

#019    HandleKeystroke(GetCurrentMessage()->message, ch, repeat_count, flags);

#020  }

AutocompleteEdit::OnChar函数是WTL里的WM_CHAR消息处理,当用户键入字母时就会触发这个消息。这个函数先跳过几个不要处理的消息,最后调用函数HandleKeystroke来处理,如下:

#001  void AutocompleteEdit::HandleKeystroke(UINT message, TCHAR key,

#002                                        UINT repeat_count, UINT flags) {

冻结RichEdit的更新。

#003    ScopedFreeze freeze(this, GetTextObjectModel());

处理消息变化前的动作。

#004    OnBeforePossibleChange();

处理消息

#005    DefWindowProc(message, key, MAKELPARAM(repeat_count, flags));

处理消息变化后的动作。

#006    OnAfterPossibleChange();

#007  }

在这里为什么要进行窗口的消息冻结呢?又为什么需要进行消息处理和消息变化后处理呢?下一次再告诉你。

上一次说到处理WM_CHAR消息,当用户每键入一个字符时,万能连接框就会去进行一次查找的过程,然后把智能提示信息显示出来。说到AutocompleteEdit::HandleKeystroke函数的操作,那么它为什么需要冻结这个函数的使用呢?现在就来分析这部份的内容。如下:

ScopedFreeze freeze(this, GetTextObjectModel());

在这行代码里,首先会调用函数GetTextObjectModel()来获取一个文档ITextDocument接口,然后再使用它的功能。这个函数的代码如下:

#001  ITextDocument* AutocompleteEdit::GetTextObjectModel() const {

先判断这个接口是否获取到,如果已经获取到就不再去重复获取了。

#002    if (!text_object_model_) {

#003      // This is lazily initialized, instead of being initialized in the

#004      // constructor, in order to avoid hurting startup performance.

这里使用了智能指针来获取IRichEditOle接口。

#005      CComPtr<IRichEditOle> ole_interface;

获取到的IRichEditOle接口绑定到智能指针里。

#006      ole_interface.Attach(GetOleInterface());

下面通过=操作符获取ITextDocument接口,如果你深入去分析这个赋值操作符,会看到它自动去调用IRichEditOle的接口IUnknown::QueryInterface来查询到ITextDocument接口,这个过程对于程序员来说是完全不用关心的,这就是使用mutable CComQIPtr<ITextDocument> text_object_model_定义的作用。

#007      text_object_model_ = ole_interface;

#008    }

#009    return text_object_model_;

#010  }

通过上面的分析,可见使用CComQIPtr<ITextDocument>智能指针可以省了很多COM调用的操作,这真是模板类的强大功能的使用之处。当把ITextDocument接口获取回来之后,对于RichEdit操作就可以轻松访问了,ScopedFreeze类生成一个局部对象,这个对象实现了对RichEdit自动冻结和解冻结的功能,这个过程是通过局部对象在栈里生命周期的特性应用。如下面的代码:

#001  AutocompleteEdit::ScopedFreeze::ScopedFreeze(AutocompleteEdit* edit,

#002                                              ITextDocument* text_object_model)

#003      : edit_(edit),

#004        text_object_model_(text_object_model) {

#005    // Freeze the screen.

#006    if (text_object_model_) {

#007      long count;

#008      text_object_model_->Freeze(&count);

#009    }

#010  }

#011

#012  AutocompleteEdit::ScopedFreeze::~ScopedFreeze() {

#013    // Unfreeze the screen.

#014    // NOTE: If this destructor is reached while the edit is being destroyed (for

#015    // example, because we double-clicked the edit of a popup and caused it to

#016    // transform to an unconstrained window), it will no longer have an HWND, and

#017    // text_object_model_ may point to a destroyed object, so do nothing here.

#018    if (edit_->IsWindow() && text_object_model_) {

#019      long count;

#020      text_object_model_->Unfreeze(&count);

#021      if (count == 0) {

这里需要手动地更新窗口的显示。

#022        // We need to UpdateWindow() here instead of InvalidateRect() because, as

#023        // far as I can tell, the edit likes to synchronously erase its background

#024        // when unfreezing, thus requiring us to synchronously redraw if we don't

#025        // want flicker.

#026        edit_->UpdateWindow();

#027      }

#028    }

#029  }

从上面的代码可以看到构造函数里冻结,析构造函数里解冻结,如果需要就会自动更新窗口的显示。

通过上面的分析,学会使用RichEdit的冻结窗口的输入,并且解冻结和更新窗口的显示,也同时学会使用智能指针来操作COM接口的方便性,最后还学会了使用栈对象的生命周期来方便对加锁和解锁的操作,以便降低代码的出错率。

为了处理字符消息实现自动完成的功能,这是怎么样实现的呢?其实是先记录字符消息响应前的字符串以及选中状态,接着再处理消息,最后才查询可能的输入,做出智能提示。

#001  void AutocompleteEdit::OnBeforePossibleChange() {

#002    // Record our state.

记录当前已经输入的字符串。

#003    text_before_change_ = GetText();

记录当前选中的字符位置。

#004    GetSelection(sel_before_change_);

#005    select_all_before_change_ = IsSelectAll(sel_before_change_);

#006  }

上面就保存字符消息响应前的状态,接着下来就是消息响应后的处理了,如下:

#001  bool AutocompleteEdit::OnAfterPossibleChange() {

#002    // Prevent the user from selecting the "phantom newline" at the end of the

#003    // edit.  If they try, we just silently move the end of the selection back to

#004    // the end of the real text.

判断用户新选中状态。

#005    CHARRANGE new_sel;

#006    GetSelection(new_sel);

#007    const int length = GetTextLength();

#008    if ((new_sel.cpMin > length) || (new_sel.cpMax > length)) {

#009      if (new_sel.cpMin > length)

#010        new_sel.cpMin = length;

#011      if (new_sel.cpMax > length)

#012        new_sel.cpMax = length;

#013      SetSelectionRange(new_sel);

#014    }

判断用户是否输入字符有变化。

#015    const bool selection_differs = (new_sel.cpMin != sel_before_change_.cpMin) ||

#016        (new_sel.cpMax != sel_before_change_.cpMax);

#017

#018    // See if the text or selection have changed since OnBeforePossibleChange().

#019    const std::wstring new_text(GetText());

#020    const bool text_differs = (new_text != text_before_change_);

#021

#022    // Update the paste state as appropriate: if we're just finishing a paste

#023    // that replaced all the text, preserve that information; otherwise, if we've

#024    // made some other edit, clear paste tracking.

#025    if (paste_state_ == REPLACING_ALL)

#026      paste_state_ = REPLACED_ALL;

#027    else if (text_differs)

#028      paste_state_ = NONE;

#029

如果输入没有任何变化,就返回去。

#030    // If something has changed while the control key is down, prevent

#031    // "ctrl-enter" until the control key is released.  When we do this, we need

#032    // to update the popup if it's open, since the desired_tld will have changed.

#033    if ((text_differs || selection_differs) &&

#034        (control_key_state_ == DOWN_WITHOUT_CHANGE)) {

#035      control_key_state_ = DOWN_WITH_CHANGE;

#036      if (!text_differs && !popup_->is_open())

#037        return false;  // Don't open the popup for no reason.

#038    } else if (!text_differs &&

#039        (inline_autocomplete_text_.empty() || !selection_differs)) {

#040      return false;

#041    }

#042

#043    const bool had_keyword = !is_keyword_hint_ && !keyword_.empty();

#044

下面开始设置新的显示字符串。

#045    // Modifying the selection counts as accepting the autocompleted text.

#046    InternalSetUserText(UserTextFromDisplayText(new_text));

#047    has_temporary_text_ = false;

#048

#049    if (text_differs) {

#050      // When the user has deleted text, don't allow inline autocomplete.  Make

#051      // sure to not flag cases like selecting part of the text and then pasting

#052      // (or typing) the prefix of that selection.  (We detect these by making

#053      // sure the caret, which should be after any insertion, hasn't moved

#054      // forward of the old selection start.)

#055      just_deleted_text_ = (text_before_change_.length() > new_text.length()) &&

#056        (new_sel.cpMin <= std::min(sel_before_change_.cpMin,

#057                                  sel_before_change_.cpMax));

#058

#059      // When the user doesn't have a selected keyword, deleting text or replacing

#060      // all of it with something else should reset the provider affinity.  The

#061      // typical use case for deleting is that the user starts typing, sees that

#062      // some entry is close to what he wants, arrows to it, and then deletes some

#063      // unnecessary bit from the end of the string.  In this case the user didn't

#064      // actually want "provider X", he wanted the string from that entry for

#065      // editing purposes, and he's no longer looking at the popup to notice that,

#066      // despite deleting some text, the action we'll take on enter hasn't changed

#067      // at all.

这里删除已经选择的提示。

#068      if (!had_keyword && (just_deleted_text_ || select_all_before_change_)) {

#069        popup_->manually_selected_match_.Clear();

#070      }

#071    }

#072

#073    // Disable the fancy keyword UI if the user didn't already have a visible

#074    // keyword and is not at the end of the edit.  This prevents us from showing

#075    // the fancy UI (and interrupting the user's editing) if the user happens to

#076    // have a keyword for 'a', types 'ab' then puts a space between the 'a' and

#077    // the 'b'.

#078    disable_keyword_ui_ = (is_keyword_hint_ || keyword_.empty()) &&

#079        ((new_sel.cpMax != length) || (new_sel.cpMin != length));

#080

更新智能提示菜单。

#081    UpdatePopup();

#082

#083    if (!had_keyword && !is_keyword_hint_ && !keyword_.empty()) {

#084      // Went from no selected keyword to a selected keyword. Set the affinity to

#085      // the keyword provider.  This forces the selected keyword to persist even

#086      // if the user deletes all the text.

#087      popup_->manually_selected_match_.Clear();

#088      popup_->manually_selected_match_.provider_affinity =

#089          popup_->autocomplete_controller()->keyword_provider();

#090    }

#091

当自动完成框字符串发生变化,就需要更新URL重点显示。

#092    if (text_differs)

#093      TextChanged();

#094

#095    return true;

#096  }

在这个函数里,先判断字符串是否发生变化,然后根据变化来决定是否更新编辑框的显示,同时还需要UpdatePopup更新智能提示菜单,最后判断是否有一个URL地址,如果有就重点显示出来。

其实这里最关键的问题就是智能菜单的数据从那里来的呢?怎么样根据用户的输入查找到最合适的提示呢?下一次我们再来分析这方面的问题。

上一次已经分析到输入字符后,就需要把这些关键字去查找历史的连接,或者相关的内容,那么可多米的浏览器又是从那里去找到这些数据呢?现在就来分析这方面相关的内容。它主要通下面的函数来实现:

#001  void AutocompleteEdit::UpdatePopup() {

冻结输入。

#002    ScopedFreeze freeze(this, GetTextObjectModel());

设置正在输入过程中。

#003    SetInputInProgress(true);

#004

如果输入的EDIT框没有焦点,就直接返回。

#005    if (!has_focus_) {

#006      // When we're in the midst of losing focus, don't rerun autocomplete.  This

#007      // can happen when losing focus causes the IME to cancel/finalize a

#008      // composition.  We still want to note that user input is in progress, we

#009      // just don't want to do anything else.

#010      //

#011      // Note that in this case the ScopedFreeze above was unnecessary; however,

#012      // we're inside the callstack of OnKillFocus(), which has already frozen the

#013      // edit, so this will never result in an unnecessary UpdateWindow() call.

#014      return;

#015    }

#016

#017    // Figure out whether the user is trying to compose something in an IME.

判断是否从输入法打开,如果是就从输入法窗口里获取字符串。

#018    bool ime_composing = false;

#019    HIMC context = ImmGetContext(m_hWnd);

#020    if (context) {

#021      ime_composing = !!ImmGetCompositionString(context, GCS_COMPSTR, NULL, 0);

#022      ImmReleaseContext(m_hWnd, context);

#023    }

#024

#025    // Don't inline autocomplete when:

#026    //   * The user is deleting text

#027    //   * The caret/selection isn't at the end of the text

#028    //   * The user has just pasted in something that replaced all the text

#029    //   * The user is trying to compose something in an IME

获取当前选择的字符串。

#030    CHARRANGE sel;

#031    GetSel(sel);

根据用户输入的字符串来查找智能提示菜单的内容。

#032    popup_->StartAutocomplete(user_text_, GetDesiredTLD(),

#033        just_deleted_text_ || (sel.cpMax < GetTextLength()) ||

#034        (paste_state_ != NONE) || ime_composing);

#035  }

在这个函数里主要调用类AutocompletePopupModel的函数StartAutocomplete来完成智能提示。而类AutocompletePopupModel的声明如下:

class AutocompletePopupModel : public ACControllerListener, public Task {

public:

AutocompletePopupModel(const ChromeFont& font,

AutocompleteEdit* editor,

Profile* profile);

~AutocompletePopupModel();

从这个类里可以看到它是继承类ACControllerListener,说明它是响应一个返回结果的监听器;继承类Task说明它是一个任务线程类。由这两个类可以看出,它是把关键字给一个线程,然后让这个线程去查询结果,当结果返回时,就再更新到显示窗口里。

虽然上面理解它的查询过程了,但是向谁查询呢?这是一个一定需要了解的问题。现在就来分析类AutocompleteController,它在构造函数时,就会创建三个查询的对象:

#001  AutocompleteController::AutocompleteController(ACControllerListener* listener,

#002                                                Profile* profile)

#003      : listener_(listener) {

#004    providers_.push_back(new SearchProvider(this, profile));

#005    providers_.push_back(new HistoryURLProvider(this, profile));

#006    keyword_provider_ = new KeywordProvider(this, profile);

#007    providers_.push_back(keyword_provider_);

#008    if (listener) {

#009      // These providers are async-only, so there's no need to create them when

#010      // we'll only be doing synchronous queries.

#011      history_contents_provider_ = new HistoryContentsProvider(this, profile);

#012      providers_.push_back(history_contents_provider_);

#013    } else {

#014      history_contents_provider_ = NULL;

#015    }

#016    for (ACProviders::iterator i(providers_.begin()); i != providers_.end(); ++i)

#017      (*i)->AddRef();

#018  }

从上面的代码,可以看到它是向SearchProvider、HistoryURLProvider、KeywordProvider和HistoryContentsProvider来查找到合适的智能提示。类SearchProvider是从搜索引擎里查找合适的内容;类HistoryURLProvider是从历史的URL里查找合适的内容;类KeywordProvider是从关键字搜索引擎里查找合适的内容;类HistoryContentsProvider是从历史内容里查找合适内容。从上面四种智能提示里,在以前的浏览器里一般只能做到从历史的URL里提示,现在“可多米”可以做到从搜索引擎和关键字引擎里查找到相应的结果回来,可见它是智能提示完美的体现,智能的水平可想而知了。这就是强大的云计算典型应用,如果没有强大的服务器群是做不到几亿人输入关键字时,还能快速返回结果的。

分析到这里,也许知道为什么GOOGLE开发浏览器的原因了吧,如果其它浏览是不可能采用这样的技术来分析用户的输入的,顶多是到历史记录里查找一下就算了。

虽然提供这么强大的搜索,它们又是怎么样实现的呢?下一次再来分析它们。

from: http://blog.csdn.net/caimouse/article/details/2954044

谷歌chrome浏览器的源码分析(二)相关推荐

  1. 谷歌chrome浏览器的源码分析(四)

    上一次说到需要调用这个OpenURLFromTab函数,那么这个函数是做什么的呢?从名称上可能猜到它是打开网页,但是是从目前TAB页里打开呢?还是新建一个?或者使用每个TAB页一个进程呢?这些疑惑,只 ...

  2. 谷歌chrome浏览器的源码分析(一)

    随着网络技术的发展,越来越多应用都已经离不开网络,特别像人类大脑一样的知识库的搜索引擎,更加是离不开功能强大的云计算.不过,即便云计算非常强大,但它还不能直接地把结果呈现给用户,这样就需要一个客户端来 ...

  3. 谷歌chrome浏览器的源码分析(五)

    上一次说到类RenderThread和类RenderView把消息处理,那么这两个类是怎么样处理消息的呢?又是怎么样处理浏览的消息呢?现在就带着这两个问题去分析它的源码,理解它处理消息的方法.类Ren ...

  4. 谷歌chrome浏览器的源码分析(六)

    消息的流通过程,是一个不同类相互交流的过程,如果不了解这个过程,根本就不知道这些类是怎么样相互协作的.由于上一次说到ViewHostMsg_RequestResource消息已经发送出来,它的处理过徎 ...

  5. 谷歌chrome浏览器的源码分析(七)

    上一次说到通过管道把接收到的HTTP数据通知另一个线程处理,它不是直接发送数据过去,而是把数据在共享内存里的句柄发送过去,达到高效通讯的目的.下面就来分析资源处理进程里,接收到这个消息之后,做些什么处 ...

  6. 谷歌chrome浏览器的源码分析(三)

    上一次介绍到怎么样从其它地方返回搜索到的超级连接,现在就来分析一下使用搜索引擎去查找的类SearchProvider,它是通过搜索引擎来查找出来的,在这里是通过GOOGLE搜索引擎来查找出来.它的声明 ...

  7. SpringBoot源码分析(二)之自动装配demo

    SpringBoot源码分析(二)之自动装配demo 文章目录 SpringBoot源码分析(二)之自动装配demo 前言 一.创建RedissonTemplate的Maven服务 二.创建测试服务 ...

  8. 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  9. gSOAP 源码分析(二)

    gSOAP 源码分析(二) 2012-5-24 flyfish 一 gSOAP XML介绍 Xml的全称是EXtensible Markup Language.可扩展标记语言.仅仅是一个纯文本.适合用 ...

最新文章

  1. Numpy入门教程:10. 统计相关
  2. .so 依赖目录 cmake_cmake浅析
  3. 快速解读linq语法
  4. C# 开发圆角控件的具体实现
  5. 今天发现新大陆:haml和Emmet
  6. iphonex价格_正二品:帮你解读IphoneX回收价,为何二手市场iphoneX依旧吃香
  7. 数据结构与算法(C#实现)系列---树
  8. CF932E Team Work
  9. 使用git restore命令撤销你在工作区的修改
  10. This view is not constrained It is olny designtime positions,so it will jump.......的解决方法
  11. cad多段线画圆弧方向_(cad多段线画圆弧方向)在cad中如何使用excel画样条曲线
  12. VC6/VS6定制应用程序按键与贴图错位问题的排查及解决
  13. 自动控制原理知识点梳理——6.线性系统的校正方法
  14. 后缀是lnk是什么文件_后缀是lnk文件怎么打开,lnk什么格式
  15. 【腾讯TMQ】漫步VR——Unity语音聊天室开发小结
  16. 计算机音乐作曲排名2019,2019全球歌曲排行榜_数据 2019全球古典音乐排行榜
  17. 外观模式(Facade)----设计模式
  18. Hyperf 初体验-ab压测
  19. Android studio 打开monitor 出现error提示 的解决办法 特别是使用了JDK-9
  20. 【模型加速】自定义TensorRT NMS3D插件(1)

热门文章

  1. 【项目实战】基于python的 p2p 贷后指标全自动日报制作
  2. 一流投资人会关注什么样的区块链初创公司?
  3. GMIS 2017大会杨强演讲:迁移学习的挑战和六大突破点
  4. GMIS 2017 Martin Müller演讲:深度学习时代的启发式搜索
  5. 58同城创始人姚劲波:未来十年是中国创业最好机会
  6. 看样子还是:JSP和JAVA
  7. Java 高并发_JAVA并发编程与高并发解决方案 JAVA高并发项目实战课程 没有项目经验的朋友不要错过!...
  8. [搜索]一种改进的召回率准确率公式计算方式
  9. APM - 零侵入监控JDBC服务
  10. 白话Elasticsearch08-深度探秘搜索技术之基于boost的细粒度搜索条件权重控制