Chinaunix首页 | 论坛 | 博客
  • 博客访问: 315555
  • 博文数量: 174
  • 博客积分: 3061
  • 博客等级: 中校
  • 技术积分: 1740
  • 用 户 组: 普通用户
  • 注册时间: 2006-05-04 22:43
文章分类

全部博文(174)

文章存档

2011年(54)

2010年(14)

2009年(30)

2008年(26)

2007年(27)

2006年(23)

我的朋友

分类: WINDOWS

2011-05-24 14:42:59

webkit.org 中有一篇文章讲述how to load frame,下面结合qt的demobrowser来讲述一下这个过程。



第一个流程: browser.exe 启动后默认加载的homepage这个过程是怎样的? 


1.   WebView *webView = new WebView; // tabwidget  中
    创建一个webview,注意这儿webview是自己的代码,它派生自qwebview

2.    webView->webPage = new WebPage ; // webview 中

      webview创建一个webpage, 这儿webpage是派生自qwebpage

3.    qwebpage的构造函数中 d(new QWebPagePrivate(this)) , 其中d 是QWebPagePrivate*

4. QWebPagePrivate::QWebPagePrivate(QWebPage *qq) 构造中

     a) 记录了QWebPage*
     b) 初始化了jsc线程环境
     c) 设置本地加载安全选项
     d) create ChromeClientQt   chromeClient = new ChromeClientQt(q);
        这儿ChromeClientQt  派生自 ChromeClient 

        这儿ChromeClient 应该是webkit定义的一个必须实现的接口,用以实现ui的一些细节。
        chromeclientqt 还有一个eventloop变量,QEventLoop.
     e) contextMenuClient = new ContextMenuClientQt(); // chromeclient 类似
     f) 最重要的是,创建了Page对象 
 page = new Page(chromeClient, contextMenuClient, editorClient,
                    new DragClientQt(q), new InspectorClientQt(q), 0, 0);

     这个对象按照我的理解,应该是webkit意义上的一个网页的root。
     之前看到的qtwebview,qtwebpage都只是外部包装。

     这儿可以看到webkit的一个重要设计,也可以成为依赖注入,即webkit将那些具体实现抽象为一些接口,
     具体表现为Page的构造函数的参数,即这些是有特定的webkitport来提供实现体的,当page需要做一些具体操作时候都通过这些接口发生呼叫。

     从这些接口,基本上夜可以看出被剥离出去的实现是   外观展示方面的比如chromeclientqt,上下文菜单的contextmenuqt,拖拽的,调试的等等。

     举例来说常用的js函数的alert 最终在浏览器环境下,最终呼叫的是通过chromeclient实现体内的实现。
      
4.1. 进入Page的构造。 page对象仔细观察发现是个非常庞大的对象,它具有很多逻辑在里面:

   a) 构造了一个Chrome对象,注意这个对象是个proxy对象,它的具体实现是采用了chromeclientqt。
      而这个对象又实现了一个HostWnd接口, 当然这个接口最终的实现是通过chromeclientqt来实现的,
      这个手法,首先通过HostWnd定义了一个Page运行需要一个宿主窗口,并且定义了它的行为; 第二通过       外部的实现注入了具体的ChromeClient的实现。所以代码中比较多见, 
      void Chrome::invalidateWindow(const IntRect& updateRect, bool immediate)
      {
       //m_client is a ChromeClient* impl by ChromeClientQt
       m_client->invalidateWindow(updateRect, immediate);
      }

      另外的contextmenuclient,editorclient也类似,不过有一点不同,它们没有专门再写一个雷比如叫做editclient 然后再使用editclientqt的impl转发,我看了下代码,原因应该是chromeclient or chrom承担的ui业务相对更多,所以chrome这个类似实际上在转发呼叫impl的同时部分逻辑也做过写额外处理。
  即完全使用impl不能完成。

   b)创建了一个setting对象

   c) 调试,插件行为的一些处理

  
4.2 将当前page加入history ? 这个有待后续深入研究。

后续就是创建一个frame了,所以从这点上来说也大致可以看出webkit的结构,貌似是page ---》 frame;


5. QWebFrame *QWebPage::mainFrame()被触发
6. void QWebPagePrivate::createMainFrame()
7. 这个时候一个临时对象QWebFrameData 出现了
void QWebPagePrivate::createMainFrame()
{
    if (!mainFrame) {
        QWebFrameData frameData(page);
        mainFrame = new QWebFrame(q, &frameData);

        emit q->frameCreated(mainFrame);
    }
}

7.1 创建了一个frameloaderclient;
    frameLoaderClient = new FrameLoaderClientQt();

    从webkit的文档,大致可以看到一个frameloader需要做的事情
    a) 下载
    b) 创建documentloader
    c) 还有很多,后续继续观察

7.2    // 创建一个frame
    frame = Frame::create(page, ownerElement, frameLoaderClient);

    这是一个重要过程,因为frame对象本身很重要.

    Frame::Frame(Page* page, HTMLFrameOwnerElement* ownerElement, FrameLoaderClient* frameLoaderClient)

    手法和之前创建page类似,也将和加载,下载等细节操作impl 给了frameloaderclient;

   先来看看frame的几个重要成员
        Page* m_page;
        mutable FrameTree m_treeNode;
        mutable FrameLoader m_loader;
        mutable RedirectScheduler m_redirectScheduler;

        mutable RefPtr m_domWindow;
        HashSet m_liveFormerWindows;

        HTMLFrameOwnerElement* m_ownerElement;
        RefPtr m_view;
        RefPtr m_doc;

        ScriptController m_script;

   我们大致可以推断,webkit中的frame实际上至少具有
   a) 内置一个frametree,这个后续可以继续观察。
   b) 有一个frameloader ,后续观察
   c) 有一个domwindow属于这个frame
   d) 有一个ownerelement,现在还不知道干嘛的 
   e) 有一个frameview ,现在不知道?
   f) document 貌似属于frame

  读到这儿,我强烈想继续指导frame,page,doc,view 这些是如何关系,如何关联?

7.2.1   if (!ownerElement) {
        page->setMainFrame(this);

本测试用例中,ownerelement为null;所以为page设置了mainframe;

所以貌似可以做出一个推断,page 其实只有一个mainframe,这个貌似也能从page的代码得到佐证;

另外frame本身则可以含有一个frametree,这个后续继续观察? 至少page和frame的关系这儿可以得到验证。



7.3  mainFrame = new QWebFrame(q, &frameData);

  frameData含有了一个frame,这儿qtwebpageprivate的
void QWebPagePrivate::createMainFrame()
{
    if (!mainFrame) {
        QWebFrameData frameData(page);
        mainFrame = new QWebFrame(q, &frameData);

        emit q->frameCreated(mainFrame);
    }
}

QWebFrame::QWebFrame(QWebPage *parent, QWebFrameData *frameData)
    : QObject(parent)
    , d(new QWebFramePrivate)
{
    d->page = parent;
    d->init(this, frameData);

    if (!frameData->url.isEmpty()) {
        WebCore::ResourceRequest request(frameData->url, frameData->referrer);
        d->frame->loader()->load(request, frameData->name, false);
    }
}

从这儿qt貌似确实将qewebpage作为qwebframe的parent呵呵,又验证了page确实含有frame;

QWebFramePrivate  这个对象的用法在qt的部分qwebXXX都有类似用法。。。

7.3.1 d->init(this, frameData);

  a) 设置了frameloaderclient的qwebframe,webframe两个对象
     这个过程中由于qwebframe就绪了,所以设置了一些关注qwebframe的signal

  b) 对frame执行init操作

      void FrameLoader::init()

      frameloader在webkit的文档中有描述 "The FrameLoader is in charge of loading documents into Frames."  

  
// Document loaders for the three phases of frame loading. Note that while 
    // a new request is being loaded, the old document loader may still be referenced.
    // E.g. while a new request is in the "policy" state, the old document loader may
    // be consulted in particular as it makes sense to imply certain settings on the new loader.
    RefPtr m_documentLoader;
    RefPtr m_provisionalDocumentLoader;
    RefPtr m_policyDocumentLoader;

 setPolicyDocumentLoader(m_client->createDocumentLoader(ResourceRequest(KURL(ParsedURLString, "")), SubstituteData()).get());
    setProvisionalDocumentLoader(m_policyDocumentLoader.get());
    setState(FrameStateProvisional);

之后最终呼叫 (这个和具体的操作相关,至少目前这个首次打开新页面是如此)
void FrameLoader::transitionToCommitted(PassRefPtr cachedPage)
{ ....
 setDocumentLoader(m_provisionalDocumentLoader.get());
    setProvisionalDocumentLoader(0);
    setState(FrameStateCommittedPage);
...
}

这样documentloader进入一个commited..,触发进入.
void FrameLoaderClientQt::transitionToCommittedForNewPage()

... void Frame::createView(const IntSize& viewportSize,

从这个角度推测,貌似frame确实和一个view关联...


    setView(0);

    RefPtr frameView;
    if (isMainFrame) {
        frameView = FrameView::create(this, viewportSize);
        frameView->setFixedLayoutSize(fixedLayoutSize);
        frameView->setUseFixedLayout(useFixedLayout);
    } else
        frameView = FrameView::create(this);

    frameView->setScrollbarModes(horizontalScrollbarMode, verticalScrollbarMode, horizontalLock, verticalLock);

    setView(frameView);

从上面这段代码来看,创建了一个mainframeview。。。

并且将它设置为当前frame的view

而frameview 的父类是scrollview,因此可以理解为这个view还确实是ui含义上的一个视图。

frameview创建之后,这个view已经和frame关联完成。


  之后的frameloader::init 创建了document对象

   void FrameLoader::init()

d ) void Document::attach()

   RenderView 在这个过程中创建,具体细节以后再说。

e) void FrameLoader::begin(const KURL& url, bool dispatch, SecurityOrigin* origin) 的继续执行导致

....   m_frame->domWindow()->setURL(document->url());
  ........ m_domWindow = DOMWindow::create(const_cast(this));

从而使得domwindow对象被创建.

整体上来说,第一阶段就是创建page,frame,view,document 等的关系,即将对象先建立起来。

第二阶段: void WebView::loadUrl(const QUrl &url)
{
    m_initialUrl = url;
    load(url);
}



// 从阅读browser代码,发现貌似有些效率问题

1. 在首次创建webview之后,会触发如上所述的一系列构建page,frame,document,loader等的过程,并且不管是否本次过程用户是否需要访问url,都一致使用about:blank, 所以整个过程进行了两次相当于。



自己写了一个简单的html, 通过抓包来观察过程。。。

"">
New Web Project







描述的过程。

第一个过程: bool DocumentLoader::startLoadingMainResource(unsigned long identifier)






最后: 

发送出如下信息..

GET /test.html HTTP/1.1
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN) AppleWebKit/533.3 (KHTML, like Gecko) demobrowser/0.1 Safari/533.3
Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Connection: Keep-Alive
Accept-Encoding: gzip
Accept-Language: zh-CN,en,*
Host: 10.1.173.6


然后服务端返回了test.html 的content。

"">
New Web Project





这个就是mainresourceloader下载下来的。。。。

注意哦,这个时候全局的js函数还没有机会执行。。。。




通过上面这张图,可以看到在mainresourceloader拿到html content之后,开始初始化scriptcontrol.


之后依然在主线程中,全局的alert得到了机会执行。。。。


不过很悲剧的是我自己编译的qtwebkitd.dll  在添加alert之后会导致gui重入引起的crash,release编译的就没有问题,现在还不知道为什么,所以先注释掉了alert,继续观察subresourceloader。。





果然如愿看到了subresourceloader1的启动, 大致来说是html 下载完之后触发的。

更加精确来说应该是 html下载完成,内嵌的js得到执行后;

之后开始现在嵌入的资源,比如图片,脚本;  因此诸如dojo。addonload 这种通知这个时候肯定还发布出来,因为嵌入的js文件还没有开始下载; 


整理一下思路:

1. html 内容下载
2. html内的内嵌js执行
3. 开始下载html ref的脚本和image等其他资源
4. 这些js下载之后也得到机会执行; 比如dojo.addonload 回调
5. 素有的外部资源下载结束,那么 window.onload 的到机会执行

另外如果通过firebug观察,会发现有个domcontentready的事件.

再仔细研究下

看看新的代码 



global ctx page, just for debug purpose




1. firebug首先看到的是enter 这个alert弹出,注意这个时候


 这个时候发现,css,2个js,一个html都已经下载了,而alert也弹出来了,不过domcontentload事件还没有触发 。

事实上即使将 css文件放到body后面,在enter弹出来时候它也已经下载或者下载中了。

但是后面那张图片却还没有下载。



2.  越过enter这个elert

发现dojo的addonload被调用了,图片也下载了,且显示domcontentloaded完成。
因此貌似可以得出一个结论,内嵌的非外链的js 貌似较image下载先。

然后我做了个测试,将alert enter,移动到图片的代码后面,就发现alert出来时候,图片也下载了。

因此大概可以看出html文件被下载后,webkit应该对js,css样式做了优化处理,可以得以异步立刻下载,而图片还是需要等待之前的js执行完成。

domcontentload必须在所有js执行完成后才会触发。

3. 之后出现domcontentload的alert ,以及onload的alert

结合一些文档,domcontentload 和window.onload的区别是前者dom解析完成即可,后者需要等待外部图片等。


通过上面几个例子大致可以看出webkit加载执行这些页面的特征。



下面再通过debug 方式看看具体的事件,加深了解。



1. mainresource 加载完成后



可以看到mainresourceload后,就开始了解析过程 。。


第一个被解析到的就是dojojs这个文件 。。。。


通过firebug实际上我们之前执导当第一个elert("enter")执行的时候,其他js貌似也“同时”被下载了;

通过debug qtwebkit发现,具体加载流程和firefox4有区别,firefox4的优化更加好。

经过观察,发现qtwbebkit的具体执行流同firefox4还略有差别。




下面做一个例子,了解一下js的环境是如何准备起来的?

 
total




因为之前的例子已经说明了:
1. 整个html文件下来
2. webkit 执行parsehtml,然后非内嵌的全局js得到执行机会。 

下面再debugger中观察。。。 

1.  果然如前 

   


在收到数据后parse,然后检测到script标签,然后开始执行这个脚本。

从阅读代码来看,webkit的HTMLTokenizer 类具有执行脚本的方法。

经过一段复杂的判断,比如判断是否处于viewsource mode,是否脚本后跟了frameset 标签等,最后终于开始执行脚本。。。

state = scriptExecution(ScriptSourceCode(scriptString, m_doc->frame() ? m_doc->frame()->document()->url() : KURL(), startLine), state);

这个scriptstring 就是 alert("enter");

这儿webkit引入了一个类叫做ScriptSourceCode , 这个类实际上貌似在整个webkit范畴内表示“scriptcode”。。。  

  ScriptSourceCode(const String& source, const KURL& url = KURL(), int startLine = 1)
        : m_provider(StringSourceProvider::create(source, url.isNull() ? String() : url.string()))
        , m_code(m_provider, startLine)
        , m_url(url)
    {
    }

接着看到ScriptSourceCode 实际上将主要工作托管给了StringSourceProvider.

再接下去看到

ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode) 
{
    return evaluateInWorld(sourceCode, mainThreadNormalWorld());
}

执行脚本call到了这儿,貌似在执行sourcecode的时候,一定要和一个“world”关联起来。 

想想js必须要和context 关联,因此先然为这个world就是context吧,呵呵。。

DOMWrapperWorld 就是这个world?

这儿可以大概看看这个"world" 表示啥?

DOMWrapperWorld* mainThreadNormalWorld()
{
    ASSERT(isMainThread());
    static DOMWrapperWorld* cachedNormalWorld = normalWorld(*JSDOMWindow::commonJSGlobalData());
    return cachedNormalWorld;
}


至少从这个代码上来看,dom世界是的js还确实只能在主线程中执行。
webworks是如何工作的或许以后可以看看 。

JSGlobalData* JSDOMWindowBase::commonJSGlobalData()
{
    ASSERT(isMainThread());

    static JSGlobalData* globalData = 0;
    if (!globalData) {
        globalData = JSGlobalData::createLeaked().releaseRef();
        globalData->timeoutChecker.setTimeoutInterval(10000); // 10 seconds
#ifndef NDEBUG
        globalData->mainThreadOnly = true;
#endif
        globalData->clientData = new WebCoreJSClientData(globalData);
    }

    return globalData;
}


DOMWrapperWorld* normalWorld(JSC::JSGlobalData& globalData)
{
    JSGlobalData::ClientData* clientData = globalData.clientData;
    ASSERT(clientData);
    return static_cast(clientData)->normalWorld();
}

    WebCoreJSClientData(JSC::JSGlobalData* globalData)
        : m_normalWorld(DOMWrapperWorld::create(globalData, true))
    {
        m_worldSet.add(m_normalWorld.get());
    }


globaldata 实际是一个JSGlobalData ..

同时返回的DOMWrapperWorld* 实际上是 WebCoreJSClientData 创建。


最后执行到..
 exec->globalData().timeoutChecker.start();
    Completion comp = JSC::evaluate(exec, exec->dynamicGlobalObject()->globalScopeChain(), jsSourceCode, shell);
    exec->globalData().timeoutChecker.stop();


这样大致可以看到dom 如何 同js 进行交互。


如果我们将目光放得高层一些,会发现存在如下的结构。。。


webcore , javascriptcore  它们实际上是两个相对独立的组件。 特别在chrome v8也是一个独立的js 引擎。

那么为了让webcore 或者说 html dom 和js 很好的结合起来,又引入了。。。

webcore/bindings 这个目录下的对象就是充当了webcore和javascriptcore的birdge。。。


我们可以看到scriptcontroller , ScriptValue, jshtmlXXX 很多对象。

因此如果要将qtwebkit使用v8估计只要让这个bridge 能够很好的port 到v8思路应该是如此。



阅读(12369) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~