谷歌chrome浏览器的源码分析(二)
這個輸入框的自動完成的功能,是比較智能化的。因為它會根據(jù)以往的輸入自動完成,或者智能提示所需要的連接或者內(nèi)容。
下面就來先看這個類的定義:
#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實現(xiàn)了Windows窗口,它是WTL里的窗口模板類,主要用來創(chuàng)建窗口界面類,并且使用類CRichEditCtrl作為基類,類CRichEditCtrl主要調(diào)用Windows里的編輯類。類CRichEditCommands實現(xiàn)RichEdit的命令功能。Menu::Delegate類是實現(xiàn)智能下拉式菜單的提示界面。因此,要學(xué)習(xí)開發(fā)chrome,需要先學(xué)習(xí)WTL的開發(fā),它是一套基于模板的窗口框架。下一次再仔細(xì)地分析自動完成的實現(xiàn)過程。
當(dāng)我們鍵入字母或者文字開始時,那么類AutocompleteEdit就會從窗口消息里獲取到相應(yīng)的字母或者文字,然后根據(jù)輸入的信息到本地或者網(wǎng)絡(luò)上保存的信息庫里查找相應(yīng)的輸入提示,這就是自動完成的實現(xiàn)。下面就來先分析輸入的函數(shù):
#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.
?
下面把a(bǔ)lt-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函數(shù)是WTL里的WM_CHAR消息處理,當(dāng)用戶鍵入字母時就會觸發(fā)這個消息。這個函數(shù)先跳過幾個不要處理的消息,最后調(diào)用函數(shù)HandleKeystroke來處理,如下:
#001??void AutocompleteEdit::HandleKeystroke(UINT message, TCHAR key,
#002????????????????????????????????????????UINT repeat_count, UINT flags) {
?
凍結(jié)RichEdit的更新。
#003????ScopedFreeze freeze(this, GetTextObjectModel());
?
處理消息變化前的動作。
#004????OnBeforePossibleChange();
?
處理消息
#005????DefWindowProc(message, key, MAKELPARAM(repeat_count, flags));
?
處理消息變化后的動作。
#006????OnAfterPossibleChange();
#007??}
?
在這里為什么要進(jìn)行窗口的消息凍結(jié)呢?又為什么需要進(jìn)行消息處理和消息變化后處理呢?下一次再告訴你。
上一次說到處理WM_CHAR消息,當(dāng)用戶每鍵入一個字符時,萬能連接框就會去進(jìn)行一次查找的過程,然后把智能提示信息顯示出來。說到AutocompleteEdit::HandleKeystroke函數(shù)的操作,那么它為什么需要凍結(jié)這個函數(shù)的使用呢?現(xiàn)在就來分析這部份的內(nèi)容。如下:
ScopedFreeze freeze(this, GetTextObjectModel());
在這行代碼里,首先會調(diào)用函數(shù)GetTextObjectModel()來獲取一個文檔ITextDocument接口,然后再使用它的功能。這個函數(shù)的代碼如下:
#001??ITextDocument* AutocompleteEdit::GetTextObjectModel() const {
?
先判斷這個接口是否獲取到,如果已經(jīng)獲取到就不再去重復(fù)獲取了。
#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接口,如果你深入去分析這個賦值操作符,會看到它自動去調(diào)用IRichEditOle的接口IUnknown::QueryInterface來查詢到ITextDocument接口,這個過程對于程序員來說是完全不用關(guān)心的,這就是使用mutable CComQIPtr<ITextDocument> text_object_model_定義的作用。
?
#007??????text_object_model_ = ole_interface;
#008????}
#009????return text_object_model_;
#010??}
?
通過上面的分析,可見使用CComQIPtr<ITextDocument>智能指針可以省了很多COM調(diào)用的操作,這真是模板類的強(qiáng)大功能的使用之處。當(dāng)把ITextDocument接口獲取回來之后,對于RichEdit操作就可以輕松訪問了,ScopedFreeze類生成一個局部對象,這個對象實現(xiàn)了對RichEdit自動凍結(jié)和解凍結(jié)的功能,這個過程是通過局部對象在棧里生命周期的特性應(yīng)用。如下面的代碼:
?
#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??}
?
從上面的代碼可以看到構(gòu)造函數(shù)里凍結(jié),析構(gòu)造函數(shù)里解凍結(jié),如果需要就會自動更新窗口的顯示。
?
通過上面的分析,學(xué)會使用RichEdit的凍結(jié)窗口的輸入,并且解凍結(jié)和更新窗口的顯示,也同時學(xué)會使用智能指針來操作COM接口的方便性,最后還學(xué)會了使用棧對象的生命周期來方便對加鎖和解鎖的操作,以便降低代碼的出錯率。
為了處理字符消息實現(xiàn)自動完成的功能,這是怎么樣實現(xiàn)的呢?其實是先記錄字符消息響應(yīng)前的字符串以及選中狀態(tài),接著再處理消息,最后才查詢可能的輸入,做出智能提示。
#001??void AutocompleteEdit::OnBeforePossibleChange() {
#002????// Record our state.
?
記錄當(dāng)前已經(jīng)輸入的字符串。
#003????text_before_change_ = GetText();
?
記錄當(dāng)前選中的字符位置。
#004????GetSelection(sel_before_change_);
#005????select_all_before_change_ = IsSelectAll(sel_before_change_);
#006??}
?
上面就保存字符消息響應(yīng)前的狀態(tài),接著下來就是消息響應(yīng)后的處理了,如下:
#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.
?
判斷用戶新選中狀態(tài)。
#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?
?
下面開始設(shè)置新的顯示字符串。
#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.
?
這里刪除已經(jīng)選擇的提示。
#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?
?
當(dāng)自動完成框字符串發(fā)生變化,就需要更新URL重點顯示。
#092????if (text_differs)
#093??????TextChanged();
#094?
#095????return true;
#096??}
?
在這個函數(shù)里,先判斷字符串是否發(fā)生變化,然后根據(jù)變化來決定是否更新編輯框的顯示,同時還需要UpdatePopup更新智能提示菜單,最后判斷是否有一個URL地址,如果有就重點顯示出來。
?
其實這里最關(guān)鍵的問題就是智能菜單的數(shù)據(jù)從那里來的呢?怎么樣根據(jù)用戶的輸入查找到最合適的提示呢?下一次我們再來分析這方面的問題。
上一次已經(jīng)分析到輸入字符后,就需要把這些關(guān)鍵字去查找歷史的連接,或者相關(guān)的內(nèi)容,那么可多米的瀏覽器又是從那里去找到這些數(shù)據(jù)呢?現(xiàn)在就來分析這方面相關(guān)的內(nèi)容。它主要通下面的函數(shù)來實現(xiàn):
#001??void AutocompleteEdit::UpdatePopup() {
?
凍結(jié)輸入。
#002????ScopedFreeze freeze(this, GetTextObjectModel());
?
設(shè)置正在輸入過程中。
#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
?
獲取當(dāng)前選擇的字符串。
#030????CHARRANGE sel;
#031????GetSel(sel);
?
根據(jù)用戶輸入的字符串來查找智能提示菜單的內(nèi)容。
#032????popup_->StartAutocomplete(user_text_, GetDesiredTLD(),
#033????????just_deleted_text_ || (sel.cpMax < GetTextLength()) ||
#034????????(paste_state_ != NONE) || ime_composing);
#035??}
?
在這個函數(shù)里主要調(diào)用類AutocompletePopupModel的函數(shù)StartAutocomplete來完成智能提示。而類AutocompletePopupModel的聲明如下:
class AutocompletePopupModel : public ACControllerListener, public Task {
?public:
??AutocompletePopupModel(const ChromeFont& font,
?????????????????????????AutocompleteEdit* editor,
?????????????????????????Profile* profile);
??~AutocompletePopupModel();
從這個類里可以看到它是繼承類ACControllerListener,說明它是響應(yīng)一個返回結(jié)果的監(jiān)聽器;繼承類Task說明它是一個任務(wù)線程類。由這兩個類可以看出,它是把關(guān)鍵字給一個線程,然后讓這個線程去查詢結(jié)果,當(dāng)結(jié)果返回時,就再更新到顯示窗口里。
?
雖然上面理解它的查詢過程了,但是向誰查詢呢?這是一個一定需要了解的問題。現(xiàn)在就來分析類AutocompleteController,它在構(gòu)造函數(shù)時,就會創(chuàng)建三個查詢的對象:
#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是從搜索引擎里查找合適的內(nèi)容;類HistoryURLProvider是從歷史的URL里查找合適的內(nèi)容;類KeywordProvider是從關(guān)鍵字搜索引擎里查找合適的內(nèi)容;類HistoryContentsProvider是從歷史內(nèi)容里查找合適內(nèi)容。從上面四種智能提示里,在以前的瀏覽器里一般只能做到從歷史的URL里提示,現(xiàn)在“可多米”可以做到從搜索引擎和關(guān)鍵字引擎里查找到相應(yīng)的結(jié)果回來,可見它是智能提示完美的體現(xiàn),智能的水平可想而知了。這就是強(qiáng)大的云計算典型應(yīng)用,如果沒有強(qiáng)大的服務(wù)器群是做不到幾億人輸入關(guān)鍵字時,還能快速返回結(jié)果的。
?
分析到這里,也許知道為什么GOOGLE開發(fā)瀏覽器的原因了吧,如果其它瀏覽是不可能采用這樣的技術(shù)來分析用戶的輸入的,頂多是到歷史記錄里查找一下就算了。
?
雖然提供這么強(qiáng)大的搜索,它們又是怎么樣實現(xiàn)的呢?下一次再來分析它們。
from:?http://blog.csdn.net/caimouse/article/details/2954044
總結(jié)
以上是生活随笔為你收集整理的谷歌chrome浏览器的源码分析(二)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SUN dataset图像数据集下载
- 下一篇: 谷歌chrome浏览器的源码分析(三)