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
alert("defer");
/*window.onload = function()
{
alert("onload");
}*/
描述的过程。
第一个过程: 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
alert("defer");
/*window.onload = function()
{
alert("onload");
}*/
这个就是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的事件.
再仔细研究下
看看新的代码
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
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
alert("enter");
因为之前的例子已经说明了:
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思路应该是如此。