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 RefPtrm_domWindow;

HashSetm_liveFormerWindows;

HTMLFrameOwnerElement* m_ownerElement;

RefPtrm_view;

RefPtrm_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.1d->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.

RefPtrm_documentLoader;

RefPtrm_provisionalDocumentLoader;

RefPtrm_policyDocumentLoader;

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

setProvisionalDocumentLoader(m_policyDocumentLoader.get());

setState(FrameStateProvisional);

之后最终呼叫 (这个和具体的操作相关,至少目前这个首次打开新页面是如此)

void FrameLoader::transitionToCommitted(PassRefPtrcachedPage)

{ ....

setDocumentLoader(m_provisionalDocumentLoader.get());

setProvisionalDocumentLoader(0);

setState(FrameStateCommittedPage);

...

}

这样documentloader进入一个commited..,触发进入.

void FrameLoaderClientQt::transitionToCommittedForNewPage()

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

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

setView(0);

RefPtrframeView;

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, 通过抓包来观察过程。。。

/span>

"">

New Web Project

alert("defer");

/*window.onload = function()

{

alert("onload");

}*/

uid-10225517-id-2968421.html

描述的过程。

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

110526144634.jpg

最后:

发送出如下信息..

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。

/span>

"">

New Web Project

alert("defer");

/*window.onload = function()

{

alert("onload");

}*/

uid-10225517-id-2968421.html

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

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

110526145906.png

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

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

110526150839.png

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

110527103834.png

果然如愿看到了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的事件.

再仔细研究下

看看新的代码

dojo.registerModulePath("com","/js");

// 全局运行环境对象

var globalAdapter;

alert("enter");

window.addEventListener('DOMContentLoaded', domloaded, false);

function domloaded()

{

alert("domloaded");

}

function onLoad()

{

alert("onload");

}

dojo.addOnLoad(function()

{

alert("addonload");

})

function onUnload()

{

}

global ctx page, just for debug purpose

loginpage_logo.png

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

110527140528.png

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

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

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

2.  越过enter这个elert

110527141352.png

发现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 加载完成后

110527144114.png

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

110527144212.png

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

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

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

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

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

total

alert("enter");

因为之前的例子已经说明了:

1. 整个html文件下来

2. webkit 执行parsehtml,然后非内嵌的全局js得到执行机会。

下面再debugger中观察。。。

1.  果然如前

110601183718.png

在收到数据后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思路应该是如此。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐