chromium历史记录实现(不完整版)

来源:互联网 发布:中国人口老龄化数据图 编辑:程序博客网 时间:2024/06/06 11:04

chromium整个历史记录功能比较复杂,包括前进后退,浏览历史,用插件以后还可以显示树形的浏览记录。这里只分析一下我所看到的部分代码,没有搞的特别清楚,主要跟前进后台的功能相关。


其实在webkit中已经支持前进后退,浏览历史等功能。这些类主要集中在third_party/WebKit/Source/WebCore/history/目录下。其中HistoryItem类标识一条基本的历史记录,除了URL外,它还主要包括缩放比例,滚动位置,表单数据等信息。该目录下的其他类:BackForwardList,BackForwardController,CachedFrame,CachedPage等也基本可以做到望文知义。所以说前进后退的逻辑webkit内核已经实现好了,只需要调用goBack, goForward接口就可以了。


但是,chromium并没有用webkit的历史记录功能,该目录下除了HistoryItem类外,其他都被无情的抛弃掉了。它在content端自己实现了历史记录的逻辑,包括可以自己定义前进后退功能的实现方法,而不仅局限于遵循webkit的逻辑。


content端相关类主要有:NavigationController(看名字就知道是个控制类型的类,可见该类包含了很多逻辑性的处理),NavigationEntry(该类与HistoryItem类相对应,其content_state_字段就是HistoryItem类序列化的结果)。也就是说chroimum将历史记录的逻辑从webcore中搬到了content端来实现。webcore负责保存状态,然后传给content。content负责判断跳转到何处,然后通过webcore打开。


下面说一下具体流程。按下后退按钮后:

#0  content::RenderViewHostImpl::Navigate (this=0x4b318f40, params=...) at content/browser/renderer_host/render_view_host_impl.cc:297
#1  0x5c0731d0 in content::WebContentsImpl::NavigateToEntry (this=0x4b067548, entry=..., reload_type=content::NavigationController::NO_RELOAD) at content/browser/web_contents/web_contents_impl.cc:1654
#2  0x5c06818c in content::NavigationControllerImpl::NavigateToPendingEntry (this=0x4b067588, reload_type=content::NavigationController::NO_RELOAD)
    at content/browser/web_contents/navigation_controller_impl.cc:1480
#3  0x5c068aea in content::NavigationControllerImpl::GoBack (this=0x4b067588) at content/browser/web_contents/navigation_controller_impl.cc:505
#4  0x5bf824d0 in content::ContentViewCoreImpl::GoBack (this=0x4b31b990) at cos/content/browser/cos/content_view_core_impl.cc:1046


向render进程发送Navigate消息,render进程收到后:

#0  WebCore::FrameLoader::loadItem (this=0x40030050, item=0x4b78c3b0, loadType=WebCore::FrameLoadTypeIndexedBackForward) at third_party/WebKit/Source/WebCore/loader/FrameLoader.cpp:3114
#1  0x5c5c5f5c in WebCore::HistoryController::recursiveGoToItem (this=0x40030298, item=0x4b78c3b0, fromItem=0x4b5cc618, type=WebCore::FrameLoadTypeIndexedBackForward)
    at third_party/WebKit/Source/WebCore/loader/HistoryController.cpp:773
#2  0x5c5c6034 in WebCore::HistoryController::goToItem (this=0x40030298, targetItem=0x4b78c3b0, type=WebCore::FrameLoadTypeIndexedBackForward)
    at third_party/WebKit/Source/WebCore/loader/HistoryController.cpp:308
#3  0x5c5f444e in WebCore::Page::goToItem (this=0x602d58a8, item=0x4b78c3b0, type=WebCore::FrameLoadTypeIndexedBackForward) at third_party/WebKit/Source/WebCore/page/Page.cpp:377
#4  0x5c16878e in WebKit::WebFrameImpl::loadHistoryItem (this=0x4abf5cd0, item=<value optimized out>) at third_party/WebKit/Source/WebKit/chromium/src/WebFrameImpl.cpp:983
#5  0x5bf58a2a in content::RenderViewImpl::OnNavigate (this=0x4ac860c0, params=...) at content/renderer/render_view_impl.cc:1150


在该消息中包含content_state_,在render中被反序列化为HistoryItem。

下一步就是根据HistoryItem发起网络请求,而实际上是去cache中取数据(cache的相关逻辑与net部分相关),并不会去取网络数据。


每当进入到一个新网页时,会把旧网页的数据最后更新同步到content端,时机是新网页第一次收到数据时:

#0  content::RenderViewImpl::SendUpdateState (this=0x4abff0c0, item=...) at content/renderer/render_view_impl.cc:1729
#1  0x5bfcd8ea in content::RenderViewImpl::UpdateSessionHistory (this=0x4abff0c0, frame=<value optimized out>) at content/renderer/render_view_impl.cc:1726
#2  content::RenderViewImpl::UpdateSessionHistory (this=0x4abff0c0, frame=<value optimized out>) at content/renderer/render_view_impl.cc:1717
#3  0x5bfd939c in content::RenderViewImpl::didCommitProvisionalLoad (this=0x4abff0c0, frame=0x4ac62cd0, is_new_navigation=<value optimized out>) at content/renderer/render_view_impl.cc:3535
#4  0x5c1d61f0 in WebKit::FrameLoaderClientImpl::dispatchDidCommitLoad (this=0x4ac62ce0) at third_party/WebKit/Source/WebKit/chromium/src/FrameLoaderClientImpl.cpp:840
#5  0x5c64137c in WebCore::FrameLoader::dispatchDidCommitLoad (this=0x4abf2050) at third_party/WebKit/Source/WebCore/loader/FrameLoader.cpp:3220
#6  WebCore::FrameLoader::dispatchDidCommitLoad (this=0x4abf2050) at third_party/WebKit/Source/WebCore/loader/FrameLoader.cpp:3215
#7  0x5c641b0a in WebCore::FrameLoader::receivedFirstData (this=0x4abf2050) at third_party/WebKit/Source/WebCore/loader/FrameLoader.cpp:606


content端的处理就是保存数据到相应的NavigationEntry。


同时,新网页也会不断更新自己的一些信息到content端:

#0  content::RenderViewImpl::UpdateURL (this=0x4abff0c0, frame=0x4ac62cd0) at content/renderer/render_view_impl.cc:1516
#1  0x5bfd94f8 in content::RenderViewImpl::didCommitProvisionalLoad (this=0x4abff0c0, frame=0x4ac62cd0, is_new_navigation=<value optimized out>) at content/renderer/render_view_impl.cc:3594
#2  0x5c1d61f0 in WebKit::FrameLoaderClientImpl::dispatchDidCommitLoad (this=0x4ac62ce0) at third_party/WebKit/Source/WebKit/chromium/src/FrameLoaderClientImpl.cpp:840
#3  0x5c64137c in WebCore::FrameLoader::dispatchDidCommitLoad (this=0x4abf2050) at third_party/WebKit/Source/WebCore/loader/FrameLoader.cpp:3220
#4  WebCore::FrameLoader::dispatchDidCommitLoad (this=0x4abf2050) at third_party/WebKit/Source/WebCore/loader/FrameLoader.cpp:3215
#5  0x5c641b0a in WebCore::FrameLoader::receivedFirstData (this=0x4abf2050) at third_party/WebKit/Source/WebCore/loader/FrameLoader.cpp:606
#6  0x5c63a6a8 in WebCore::DocumentLoader::commitData (this=0x4b34fb88, 


关于网页状态什么时候保存?实际上有改变的时候就会保存,比如缩放比例在缩放操作后会保存,滚动位置在滚动操作后会保存。比如:

#1  0x5c646400 in WebCore::HistoryController::restoreScrollPositionAndViewState (this=0x4abf2298) at third_party/WebKit/Source/WebCore/loader/HistoryController.cpp:145
#2  0x5c640f82 in WebCore::FrameLoader::didFirstLayout (this=0x4abf2050) at third_party/WebKit/Source/WebCore/loader/FrameLoader.cpp:2276
#3  0x5c6728a6 in WebCore::FrameView::performPostLayoutTasks (this=0x4acc4b98) at third_party/WebKit/Source/WebCore/page/FrameView.cpp:2454
#4  0x5c672fee in WebCore::FrameView::layout (this=0x4acc4b98, allowSubtree=<value optimized out>) at third_party/WebKit/Source/WebCore/page/FrameView.cpp:1243
#5  WebCore::FrameView::layout (this=0x4acc4b98, allowSubtree=<value optimized out>) at third_party/WebKit/Source/WebCore/page/FrameView.cpp:1000


表单数据在什么时候保存一直没找到。。。。。(脑补一下不外乎两种情况,一是在有数据改变时保存,二是在退出时统一保存。感觉第二种可能性较高。。。)


网上有高人写过一个简单的流程,这里罚抄一遍:

从地址栏打开一个URL地址:


1.当用户输入,或者地址栏收到一个回车时,自动完成编辑框会算出最终目标URL,并传给AutocompleteEdit::OpenURL。(说用户输入可能不够确切——比如,搜索结果里的一个URL。)
2.NavigationController::LoadURL里的导航控制器(NavigationController)会去打开URL。
3.NavigationController调用TabContents::Navigate,并传入一个NavigationEntry来负责页面转换。这将会导致子进程如果需要,就创建一个RenderViewHost。如果是第一次导航,就不会有RenderView,否则如果渲染器crash了,会导致它被crash里的覆盖。
4.Navigate继续调用RenderViewHost::NavigateToEntry。NavigationController存储这个导航入口,但是会打一个"pending"的标记,因为它不知道到底会不会转换(可能主机无法解析)。
5.RenderViewHost::NavigateToEntry会给渲染进程里新的RenderView发一个ViewMsg_Navigate。
6.导航时,RenderView可能会导航,可能会失败,也可能会导航到其他地方(比如用户点了一个链接)。RenderViewHost会等待RenderView发一个ViewHostMsg_FrameNavigate。
7.当载入状态被WebKit改成"committed"时(server响应并发送了数据),RenderView就会发这个消息,并被RenderViewHost::OnMsgNavigate捕获处理。
8.过程中NavigationEntry会更新载入过程中的信息。例如点击的链接,之前浏览器是不知道的。如果导航是浏览器初始化的,就像启动时那样,在导航过程中可能会由于重定向而改变URL地址。
9.NavigationController给帐户把新的信息更新到导航列表中。



导航和会话历史:


每个NavigationEntry都会存储一个页面ID和历史状态数据。页面ID用来唯一标识一个页面载入,这样我们就知道哪一个NavigationEntry与之对应。它在页面被提交时分配,所以一个pending状态的NavigationEntry的页面ID是-1。历史状态数据就是WebCore::HistoryItem序列化的一个字符串。它里面包含了页面URL,子frame URL,以及表格数据。
1.当浏览器初始化请求时(在地址栏中输入,或者点击后退/前进/刷新)
  1.生成一个WebRequest来表示导航,它会一直为书记员(bookkeeping)带着页面ID之类的信息。新导航的ID是-1。当页面是第一次访问时,旧入口的导航会把ID分配给NavigationEntry。到后面载入提交时,还会查询这个信息。
  2.主WebFrame会被调用来载入新的请求。
2.渲染器初始化请求时(用户点击链接,js改变了location,等):
  1.WebCore::FrameLoader的各个载入方法会被调用来载入请求。
3.无论什么情况,当收到服务器返回的第一个包时,载入会被提交(不再是"pending"或"provisional"状态)。
4.如果有一个新的导航,会fork出一个新的WebCore,它会创建一个新的HistoryItem,并加到BackForwardList中。用这个方法,我们可以区分哪个导航是新的,哪个是会话历史导航。
5.RenderView::DidCommitLoadForFrame处理载入的提交。在这,前一个页面的状态通过ViewHostMsg_UpdateState消息被存储在会话历史中。这告诉浏览器去更新对应的NavigationEntry(通过RenderView的当前页面ID标识)为新的历史状态。
6.RenderView的当前页面ID被更新为提交页面的ID。对于新的导航,会生成一个新的唯一页面ID。对会话历史导航,页面ID是第一次访问时的ID,初始化导航时,已经把它存在WebRequest里了。
7.发送一个ViewHostMsg_FrameNavigate消息给浏览器,更新对应的NavigationEntry(由RenderView刚更新的页面ID标识)的新URL和其他信息。