一、创建nsWebShellWindow实例,准备处理输入的WebURI地址;
因为接收用户打开一个新URI的情况比较多,如通过脚本中的window.open或直接在地址栏中输入或者在新开的一个窗口打开或者在新建的一个页签打开等等,通过对这一系列的打开一个页面的外围场景进行处理后,内核都会新建一个或利用原有的nsWebShellWindow实例来处理整个打开URI的过程。其包含的主要成员有nsComPtr
二、准备装载指定URI的内容;
首先由协调者nsDocShell的LoadURI方法来发起装载的请求,经过一番安全或请求Policy等方面的设定后创建nsCOMPtr
nsCOMPtr
rv = NS_NewChannel(getter_AddRefs(channel),
aURI, nsnull, nsnull, static_cast
由uriLoader->OpenURI(aChannel, (mLoadType == LOAD_LINK), this);来统一继续处理所有的URI请求,其中会创建一个临时的nsCOMPtr
三、根据接收的数据类型创建Document及相关ContentViewer;
根据异步数据的获取,获取数据的线程向MainThread发出nsInputStreamReadyEvent事件,由主线程来处理相关事件,其往往会调用到nsInputStreamPump中去,其关键调用如下:
nsInputStreamPump::OnInputStreamReady(nsIAsyncInputStream *stream)
{
LOG(("nsInputStreamPump::OnInputStreamReady [this=%x]\n", this));
// this function has been called from a PLEvent, so we can safely call
// any listener or progress sink methods directly from here.
for (;;) {
if (mSuspendCount || mState == STATE_IDLE) {
mWaiting = PR_FALSE;
break;
}
PRUint32 nextState;
switch (mState) {
case STATE_START:
nextState = OnStateStart();
break;
case STATE_TRANSFER:
nextState = OnStateTransfer();
break;
case STATE_STOP:
nextState = OnStateStop();
break;
}
...........
mState = nextState;
}
return NS_OK;
}
当STATE_START时,表示已获取部分
{
..........................................
// Instantiate the content viewer object
nsCOMPtr
nsresult rv = NewContentViewerObj(aContentType, request, mLoadGroup,
aContentHandler, getter_AddRefs(viewer));
NS_ENSURE_SUCCESS(Embed(viewer, "", (nsISupports *) nsnull),
NS_ERROR_FAILURE);
............................................
}
nsresult
nsDocShell::NewContentViewerObj(const char *aContentType,
nsIRequest * request, nsILoadGroup * aLoadGroup,
nsIStreamListener ** aContentHandler,
nsIContentViewer ** aViewer)
{
nsCOMPtr
nsresult rv;
nsCOMPtr
if (NS_FAILED(rv))
return rv;
nsXPIDLCString contractId;
rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", aContentType, getter_Copies(contractId));
// Create an instance of the document-loader-factory
nsCOMPtr
if (NS_SUCCEEDED(rv))
docLoaderFactory = do_GetService(contractId.get());
if (!docLoaderFactory) {
return NS_ERROR_FAILURE;
}
// Now create an instance of the content viewer
// nsLayoutDLF makes the determination if it should be a "view-source" instead of "view"
NS_ENSURE_SUCCESS(docLoaderFactory->CreateInstance("view",
aOpenedChannel,
aLoadGroup, aContentType,
static_cast
nsnull,
aContentHandler,
aViewer),
NS_ERROR_FAILURE);
(*aViewer)->SetContainer(static_cast
return NS_OK;
}
这里主要会根据contentType的不同创建不同的Document对象,它由nsContentDLF::CreateInstance来实现。其主要代码如下
nsCOMPtr
nsCOMPtr
do {
// Create the document
doc = do_CreateInstance(aDocumentCID, &rv);//针对htmldocumnt、xuldocument、imagedocument的不同会对应不同的DocumnetCID
if (NS_FAILED(rv))
break;
// Create the document viewer XXX: could reuse document viewer here!
rv = NS_NewDocumentViewer(getter_AddRefs(docv));
if (NS_FAILED(rv))
break;
docv->SetUAStyleSheet(gUAStyleSheet);
doc->SetContainer(aContainer);
// Initialize the document to begin loading the data. An
// nsIStreamListener connected to the parser is returned in
// aDocListener.
rv = doc->StartDocumentLoad(aCommand, aChannel, aLoadGroup, aContainer, aDocListener, PR_TRUE);
if (NS_FAILED(rv))
break;
// Bind the document to the Content Viewer
rv = docv->LoadStart(doc);
*aDocViewer = docv;
NS_IF_ADDREF(*aDocViewer);
} while (PR_FALSE);
其中需要注意的是在StartDocumentLoad的时候会创建一个mParser = do_CreateInstance(kCParserCID, &rv);作为DocListener,以供后面解析文档内容用;同时会创建nsCOMPtr
// create the content sink
nsCOMPtr
if (aSink)
sink = aSink;
else {
if (IsXHTML()) {
nsCOMPtr
rv = NS_NewXMLContentSink(getter_AddRefs(xmlsink), this, uri,
docShell, aChannel);
sink = xmlsink;
} else {
nsCOMPtr
rv = NS_NewHTMLContentSink(getter_AddRefs(htmlsink), this, uri,
docShell, aChannel);
sink = htmlsink;
}
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(sink,
"null sink with successful result from factory method");
}
mParser->SetContentSink(sink);
// parser the content of the URI
mParser->Parse(uri, nsnull, (void *)this);
}
一旦初步组织好nsDocShell、Document、Viewer、Parser、ContentSink之间的关系后,在nsDocumentOpenInfo调用DispatchContent时将返回的DocListener也即nsParser实例赋值给nsDocumentOpenInfo的m_targetStreamListener,以供后续使用,同时告诉Parser、ContentSink作好解析、组织文档准备;
另外通过nsDocShell的Embed方法,初始化DocumentViewer,其主要实现如下:
nsDocShell::SetupNewViewer(nsIContentViewer * aNewViewer)
{
..........................
mContentViewer = aNewViewer;
nsCOMPtr
NS_ENSURE_SUCCESS(GetMainWidget(getter_AddRefs(widget)), NS_ERROR_FAILURE);
nsCOMPtr
if (widget) {
deviceContext = do_CreateInstance(kDeviceContextCID);
NS_ENSURE_TRUE(deviceContext, NS_ERROR_FAILURE);
deviceContext->Init(widget->GetNativeData(NS_NATIVE_WIDGET));
}
nsRect bounds(x, y, cx, cy);
mContentViewer->Init(widget, deviceContext, bounds);
..........................
}
DocumentViewerImpl::InitInternal(nsIWidget* aParentWidget,
nsISupports *aState,
nsIDeviceContext* aDeviceContext,
const nsRect& aBounds,
PRBool aDoCreation,
PRBool aInPrintPreview,
PRBool aNeedMakeCX /*= PR_TRUE*/)
{
.......................................
if (aParentWidget && !mPresContext) {
// Create presentation context
if (mIsPageMode) {
//Presentation context already created in SetPageMode which is calling this method
}
else
mPresContext =
new nsPresContext(mDocument, nsPresContext::eContext_Galley);
NS_ENSURE_TRUE(mPresContext, NS_ERROR_OUT_OF_MEMORY);
nsresult rv = mPresContext->Init(aDeviceContext);
if (NS_FAILED(rv)) {
mPresContext = nsnull;
return rv;
}
if (aDoCreation && mPresContext) {
// The ViewManager and Root View was created above (in
// MakeWindow())...
rv = InitPresentationStuff(!makeCX, !makeCX);
}
................................................
}
DocumentViewerImpl::InitPresentationStuff(PRBool aDoInitialReflow, PRBool aReenableRefresh)
{
..............................................
// Create the style set...
nsStyleSet *styleSet;
nsresult rv = CreateStyleSet(mDocument, &styleSet);
NS_ENSURE_SUCCESS(rv, rv);
// Now make the shell for the document
rv = mDocument->CreateShell(mPresContext, mViewManager, styleSet,
getter_AddRefs(mPresShell));
mPresShell->BeginObservingDocument();//mDocument->AddObserver(mPresShell);
..............................................
}
nsDocument::doCreateShell(nsPresContext* aContext,
nsIViewManager* aViewManager, nsStyleSet* aStyleSet,
nsCompatibility aCompatMode,
nsIPresShell** aInstancePtrResult)
{
*aInstancePtrResult = nsnull;
NS_ENSURE_FALSE(mShellsAreHidden, NS_ERROR_FAILURE);
FillStyleSet(aStyleSet);
nsCOMPtr
nsresult rv = NS_NewPresShell(getter_AddRefs(shell));
if (NS_FAILED(rv)) {
return rv;
}
rv = shell->Init(this, aContext, aViewManager, aStyleSet, aCompatMode);
NS_ENSURE_SUCCESS(rv, rv);
// Note: we don't hold a ref to the shell (it holds a ref to us)
NS_ENSURE_TRUE(mPresShells.AppendElementUnlessExists(shell),
NS_ERROR_OUT_OF_MEMORY);
shell.swap(*aInstancePtrResult);
return NS_OK;
}
这样同时准备好与显示相关的nsDeviceContext、nsPresContext、nsPresShell,其中需要留意的是nsPresShell对象是作为nsDocument
四、解析、组织文档内容,同时作好布局
继续查看nsInputStreamPump::OnInputStreamReady(nsIAsyncInputStream *stream)方法中的STATE_TRANSFER时,表示已获取相关文档数据,此时会继续利用uriLoader临时创建的nsDocumentOpenInfo,由它的方法OnDataAvailable来继续处理获得的数据,其实现如下:
NS_IMETHODIMP nsDocumentOpenInfo::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt, nsIInputStream * inStr, PRUint32 sourceOffset, PRUint32 count)
{
// if we have retarged to the end stream listener, then forward the call....
// otherwise, don't do anything
nsresult rv = NS_OK;
if (m_targetStreamListener)
rv = m_targetStreamListener->OnDataAvailable(request, aCtxt, inStr, sourceOffset, count);
return rv;
}
此时其
Gecko内核中的Parser有HtmlParser以解析html文档;xmlParser以解析符合xml格式的文档如xul、svg;CssParser以解析Css文档等。解析器通过Tokenizer来认识文档中的标识,然后结合不同文档类型预定义的Atom及ContentSink来组织想要的文档结构。整个HtmlParser的解析过程还是蛮复杂的,不过无论解析的过程怎样,其结果就是会适时的调用ContentSink中的主要方法,如OpenHead、OpenBody、CloseBody、OpenForm、CloseForm、OpenContainer、CloseContainer、StartLayout、ProcessLINKTag、ProcessSCRIPTEndTag、ProcessSTYLEEndTag等;
其中需要留意的是在构建文档结构的同时往往需要对不同的Content Node 创建对应Frame对象,同时针对不同的Frame,还时可能还需要构建nsView,如nsBoxFrame,在一些条件下需要调用方法CreateViewForFrame来创建一个nsView实例,其主要代码如下:
{
// Create a view
nsIView *view = viewManager->CreateView(aFrame->GetRect(), parentView, visibility);
if (view) {
// Insert the view into the view hierarchy. If the parent view is a
// scrolling view we need to do this differently
nsIScrollableView* scrollingView = parentView->ToScrollableView();
if (scrollingView) {
scrollingView->SetScrolledView(view);
} else {
viewManager->SetViewZIndex(view, autoZIndex, zIndex);
// XXX put view last in document order until we can do better
viewManager->InsertChild(parentView, view, nsnull, PR_TRUE);
}
}
// Remember our view
aFrame->SetView(view);
}
NS_IMETHODIMP_(nsIView *)
nsViewManager::CreateView(const nsRect& aBounds,
const nsIView* aParent,
nsViewVisibility aVisibilityFlag)
{
nsView *v = new nsView(this, aVisibilityFlag);
if (v) {
v->SetPosition(aBounds.x, aBounds.y);
nsRect dim(0, 0, aBounds.width, aBounds.height);
v->SetDimensions(dim, PR_FALSE);
v->SetParent(static_cast
}
return v;
}
同时对于一些nsView还需要通过其nsIView::CreateWidget方法来创建原生的窗口,主要代码如下
nsresult nsIView::CreateWidget(const nsIID &aWindowIID,
nsWidgetInitData *aWidgetInitData,
nsNativeWidget aNative,
PRBool aEnableDragDrop,
PRBool aResetVisibility,
nsContentType aContentType,
nsIWidget* aParentWidget)
{
nsView* v = static_cast
v->LoadWidget(aWindowIID))
..................................
mWindow->Create(aNative, trect, ::HandleEvent, dx, nsnull, nsnull, aWidgetInitData);
..............................
}
在mWindow->Create中会接着调用如nsWindow::StandardWindowCreate、BaseCreate等;
其中
五、显示文档内容
在上面解析、组织文档结构时,当调用OpenBody时会适时调用StartLayout,其主要代码如下
{
nsPresShellIterator iter(mDocument);
nsCOMPtr
while ((shell = iter.GetNextShell())) {
// Make sure we don't call InitialReflow() for a shell that has
// already called it. This can happen when the layout frame for
// an iframe is constructed *between* the Embed() call for the
// docshell in the iframe, and the content sink's call to OpenBody().
// (Bug 153815)
PRBool didInitialReflow = PR_FALSE;
shell->GetDidInitialReflow(&didInitialReflow);
if (didInitialReflow) {
// XXX: The assumption here is that if something already
// called InitialReflow() on this shell, it also did some of
// the setup below, so we do nothing and just move on to the
// next shell in the list.
continue;
}
nsRect r = shell->GetPresContext()->GetVisibleArea();
nsCOMPtr
nsresult rv = shell->InitialReflow(r.width, r.height);
}
由nsPresShell的InitialReflow方法来启动布局,主要作用是根据文档元素的类型、属性的不同,而决定是否需要重新布局该文档元素及其子元素,一旦觉得有必要重新布局则调用nsPresShell的PostReflowEvent方法,它通过向主线程MainThread发送一个ReflowEvent,一旦MainThread接收到该ReflowEvent,其会由对应nsPresShell的DoFlushPendingNotifications方法来处理,其最终根据当前的nsView及原生窗口的不同,在windows平台上它会对原生的窗口句柄进行InvalidateRect或UpdateWindow处理,按照windows图形管理的逻辑,系统会根据相关条件,及时向该原生窗口句柄发送原生的WM_PAINT消息,而对处理原生的窗口消息,一般会由窗口创建时提供的窗口回调函数来处理,经过一番处理判断后会触发调用nsWindow::DispatchWindowEvent,进而会调用上面提到的
//
// Main events handler
//
nsEventStatus PR_CALLBACK HandleEvent(nsGUIEvent *aEvent)
{
//printf(" %d %d %d (%d,%d) \n", aEvent->widget, aEvent->widgetSupports,
// aEvent->message, aEvent->point.x, aEvent->point.y);
nsEventStatus result = nsEventStatus_eIgnore;
nsView *view = nsView::GetViewFor(aEvent->widget);
if (view)
{
view->GetViewManager()->DispatchEvent(aEvent, &result);
}
return result;
}
进而由nsViewManager::DispatchEvent统一来处理事件,这时经过一些处理,往往需要重新渲染相关View,而渲染View的主要代码如下:
void nsViewManager::RenderViews(nsView *aView, nsIRenderingContext& aRC,
const nsRegion& aRegion)
{
if (mObserver) {
nsView* displayRoot = GetDisplayRootFor(aView);
nsPoint offsetToRoot = aView->GetOffsetTo(displayRoot);
nsRegion damageRegion(aRegion);
damageRegion.MoveBy(offsetToRoot);
aRC.PushState();
aRC.Translate(-offsetToRoot.x, -offsetToRoot.y);
mObserver->Paint(displayRoot, &aRC, damageRegion);
aRC.PopState();
}
}
而上面提到的nsPresShell实例往往作为nsViewManager的一个Observer,这样工作就这样转交给nsPresShell来处理。而nsPresShell的Paint方法的主要内容如下:
PresShell::Paint(nsIView* aView,
nsIRenderingContext* aRenderingContext,
const nsRegion& aDirtyRegion)
{
AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Paint);
nsIFrame* frame;
nsresult rv = NS_OK;
if (mIsDestroying) {
NS_ASSERTION(PR_FALSE, "A paint message was dispatched to a destroyed PresShell");
return NS_OK;
}
NS_ASSERTION(!(nsnull == aView), "null view");
frame = static_cast
nscolor backgroundColor;
mViewManager->GetDefaultBackgroundColor(&backgroundColor);
for (nsIView *view = aView; view; view = view->GetParent()) {
if (view->HasWidget()) {
PRBool widgetIsTransparent;
view->GetWidget()->GetHasTransparentBackground(widgetIsTransparent);
if (widgetIsTransparent) {
backgroundColor = NS_RGBA(0,0,0,0);
break;
}
}
}
if (!frame) {
if (NS_GET_A(backgroundColor) > 0) {
aRenderingContext->SetColor(backgroundColor);
aRenderingContext->FillRect(aDirtyRegion.GetBounds());
}
return NS_OK;
}
nsLayoutUtils::PaintFrame(aRenderingContext, frame, aDirtyRegion,
backgroundColor);
return rv;
}
相关主要工作又转交给nsLayoutUtils::PaintFrame;其主要工作就是通过 nsDisplayListBuilder builder来建立一个nsDisplayList list,并调用nsDisplayList::Paint方法,其实现如下:
void nsDisplayList::Paint(nsDisplayListBuilder* aBuilder, nsIRenderingContext* aCtx,
const nsRect& aDirtyRect) const {
for (nsDisplayItem* i = GetBottom(); i != nsnull; i = i->GetAbove()) {
i->Paint(aBuilder, aCtx, aDirtyRect);
}
nsCSSRendering::DidPaint();
}
在其中的for循环中会根据DisplayList Item构成的不同,会调用不同frame的Paint方法等,如
nsDisplayText::Paint(nsDisplayListBuilder* aBuilder,
nsIRenderingContext* aCtx, const nsRect& aDirtyRect) {
static_cast
PaintText(aCtx, aBuilder->ToReferenceFrame(mFrame), aDirtyRect);
}
通过这样一个流程,就会显示出相关内容给用户。
六、总结分析
通过上面初步的整理,对整个过程有了初步的了解,其中关键在于了解到LoadURI的发起、nsIStreamListener数据的接收、nsParser及nsContentSink对文档的组织构成、nsPresShell结合nsFrame、nsView、nsViewManager、nsWindow对内容的显示。总的说来整个过程是相当的复杂,毕竟自从Web服务端获取数据后在内核中需要不断的动态创建很多
七、参考网址
The Life Of An HTML HTTP Request
没有评论:
发表评论