被蝽咬到眼皮:S60?WebKit?之基础流程分析(侧重渲染)

来源:百度文库 编辑:九乡新闻网 时间:2024/05/02 04:40:52

S60提供全局函数 CreateBrowserControlL 用以创建浏览器控件,此API已经包含在三版及五版SDK中,函数原型如下:

CBrCtlInterface* CreateBrowserControlL(
     CCoeControl* aParent,
     TRect aRect,
     TUint aBrCtlCapabilities,
     TUint aCommandIdBase,
     MBrCtlSoftkeysObserver* aBrCtlSoftkeysObserver,
     MBrCtlLinkResolver* aBrCtlLinkResolver,
     MBrCtlSpecialLoadObserver* aBrCtlSpecialLoadObserver,
     MBrCtlLayoutObserver* aBrCtlLayoutObserver,
     MBrCtlDialogsProvider* aBrCtlDialogsProvider,
     MBrCtlWindowObserver* aBrCtlWindowObserver,
     MBrCtlDownloadObserver* aBrCtlDownloadObserver);

函数实现于Webkit/BrowserControl下的BrCtl.cpp中:

EXPORT_C CBrCtlInterface* CreateBrowserControlL(CCoeControl* aParent, TRect aRect, TUint aBrCtlCapabilities,
        TUint aCommandIdBase, MBrCtlSoftkeysObserver* aBrCtlSoftkeysObserver,
        MBrCtlLinkResolver* aBrCtlLinkResolver, MBrCtlSpecialLoadObserver* aBrCtlSpecialLoadObserver,
        MBrCtlLayoutObserver* aBrCtlLayoutObserver, MBrCtlDialogsProvider* aBrCtlDialogsProvider,
        MBrCtlWindowObserver* aBrCtlWindowObserver, MBrCtlDownloadObserver* aBrCtlDownloadObserver)
{
    return CBrCtl::NewL(aParent, aRect, aBrCtlCapabilities,
            aCommandIdBase, aBrCtlSoftkeysObserver,
            aBrCtlLinkResolver, aBrCtlSpecialLoadObserver,
            aBrCtlLayoutObserver, aBrCtlDialogsProvider,
            aBrCtlWindowObserver, aBrCtlDownloadObserver);
}

可以看到,该函数仅仅只是调用了CBrCtl::NewL静态成员函数并返回结果。我们来看看CBrCtl类:

class CBrCtl : public CBrCtlInterface, public MBrCtlLoadEventObserver

该类派生自CBrCtlInterface,关于CBrCtlInterface,可以在Symbian SDK文档中查到这样的信息:

The CBrCtlInterface class is the base class of the Browser Control API.

All clients of browser control must use this class. The functions defined in the CBrCtlInterface class implement basic Browser Control functionality. You can customize the Browser Control to extend its functionality by implementing additional interface classes in the host application. Examples of such classes are MBrCtlDataLoadSupplier, MBrCtlDialogsProvider, MBrCtlDialogsProvider etc.

继续查看CBrCtlInterface的静态成员函数NewL:

CBrCtlInterface* CBrCtl::NewL(
    CCoeControl* aParent,
    TRect aRect,
    TUint aBrCtlCapabilities,
    TUint aCommandIdBase,
    MBrCtlSoftkeysObserver* aBrCtlSoftkeysObserver,
    MBrCtlLinkResolver* aBrCtlLinkResolver,
    MBrCtlSpecialLoadObserver* aBrCtlSpecialLoadObserver,
    MBrCtlLayoutObserver* aBrCtlLayoutObserver,
    MBrCtlDialogsProvider* aBrCtlDialogsProvider,
    MBrCtlWindowObserver* aBrCtlWindowObserver,
    MBrCtlDownloadObserver* aBrCtlDownloadObserver )
{
    CBrCtl* self = new (ELeave) CBrCtl(aParent, aBrCtlCapabilities, aCommandIdBase, aBrCtlSoftkeysObserver, 
              aBrCtlLinkResolver, aBrCtlSpecialLoadObserver, aBrCtlLayoutObserver, aBrCtlDialogsProvider,
              aBrCtlWindowObserver, aBrCtlDownloadObserver );

    CleanupStack::PushL(self);
    self->ConstructL(aParent, aRect);
    CleanupStack::Pop(); //self

    return self;
}

通过Symbian典型的两阶构造方式创建了一个CBrCtl实例并返回,下面看看其二阶构造干了什么:

void CBrCtl::ConstructL(CCoeControl* aParent, TRect& aRect)
{
    SetContainerWindowL(*aParent );
    iRect = aRect;

    // Setup default support, if it wasn't passed into the constructor...
    // Create and initialize the Softkey Observer
    if (iBrCtlSoftkeysObserver == NULL) {
        iBrCtlSoftkeysObserver = new (ELeave) CBrCtlSoftkeysObserver;
        iOwnSoftkeysObserver = ETrue;
    }

    // Don't create default Link Resolver, its supplied by calling app or skip
    // Create and initialize the Special Load Observer
    if (iBrCtlSpecialLoadObserver == NULL) {
        iBrCtlSpecialLoadObserver = new (ELeave) CBrCtlSpecialLoadObserver;
        iOwnSpecialLoadObserver = ETrue;
    }

    // Create and initialize the Layout Observer
    if (iBrCtlLayoutObserver == NULL) {
        iBrCtlLayoutObserver = new (ELeave) CBrCtlLayoutObserver;
        iOwnLayoutObserver = ETrue;
    }

    // Create and initialize the Dialog Provider
    if (iBrCtlDialogsProvider == NULL) {
        iBrCtlDialogsProvider = CBrowserDialogsProvider::NewL(NULL);
        iOwnDialogsProvider = ETrue;
    }

    // window observer
    if (!iBrCtlWindowObserver ) {
        iBrCtlWindowObserver = new(ELeave) CBrCtlWindowObserver;
        iOwnWindowObserver = ETrue;
    }

    if (!iBrCtlDownloadObserver ) {
        iOwnDownloadObserver = ETrue;
    }

    // Set up our capabilites, capabilities mask passed into our constructor...
    // Do we support our internal scrolling solution. Scrolling can also be
    // handled by the parent, i.e. BrowserUI, Email, SMS, or other application.
    if (Capabilities() & TBrCtlDefs::ECapabilityDisplayScrollBar) {
        iBrCtlScrollingProvider = CBrCtlScrollingProvider::NewL(*this, *this);
        iOwnScrollingProvider = ETrue;
    }

    // Create instance of CHttpLoaderEventToUiListener object
    // this has to be initialized before the browser view
    // as resource loader needs this
    iHttpLoaderEventToUiListener = CHttpLoaderEventToUiListener::NewL( *this );

    // Setup our internal support...
    // Create and initialize WebKitView
    iWebKitControl = CWebKitControl::NewL( *this );
    iBrHistoryInterface = CBrHistoryInterface::NewL(this);

    // Create history controller
    iHistoryController = CHistoryController::NewL( *this, iBrHistoryInterface );
    iWebKitControl->SetHistoryController( *iHistoryController );

    // Set the rect for BrowserControl (a CCoeControl).
    SetRect(aRect);
    ActivateL();
}

代码比较多,挑一处重点 iWebKitControl = CWebKitControl::NewL(*this ); 这里创建一个CWebKitControl的实例并赋值给了其成员变量iWebKitControl。

class CWebKitControl : public CBase

CWebKitControl类没什么特别的,简单派生自CBase。来看看其静态成员函数NewL:

CWebKitControl* CWebKitControl::NewL(CBrCtl& aBrCtl)
{
    CWebKitControl* self = new( ELeave ) CWebKitControl( aBrCtl );

    CleanupStack::PushL( self );
    self->ConstructL();
    CleanupStack::Pop();

    return self;
}

void CWebKitControl::ConstructL()
{
    // refer count static object for cleanup
    CStaticObjectContainer::Instance().Ref();
    InitializeWebCoreL();

    // settings
    InitializeSettingsL();

    // init webkit view
    // pass parent CCoeControl
    iWebKitView = CWebKitView::NewL( *iBrCtl, *this );

    // scrolling provider can be null
    iWebKitView->SetScrollingProvider( *iBrCtl->BrCtlScrollingProvider() );
    iWebKitView->SetLayoutObserver( iBrCtl->BrCtlLayoutObserver() );

    // init resloader preferences
    MWebCoreLoaderContainer* loaderContainer = TWebCoreLoaderContainer::LoaderContainer();
    loaderContainer->SetCookiesEnabled( GetBrowserSettingL( TBrCtlDefs::ESettingsCookiesEnabled ) );

    loaderContainer->SetDownloadsOpenEnabled( GetBrowserSettingL( TBrCtlDefs::ESettingsAutoOpenDownloads ) );

    loaderContainer->SetBrowserEmbedded( GetBrowserSettingL( TBrCtlDefs::ESettingsEmbedded ) );

    loaderContainer->SetLoadObservers(*(iBrCtl->BrCtlSpecialLoadObserver()),
        *(iBrCtl->BrCtlDownloadObserver()), *(iBrCtl->HttpLoaderEventToUi()) );

    LoadResourceFileL();

    iProgressTimeout = CPeriodic::NewL( CActive::EPriorityHigh );

    // generic callback timer
    iCancelTimer = CPeriodic::NewL( CActive::EPriorityHigh + 1 );
    if  (!iDefaultCharset) {
        CRepository* rep = CRepository::NewL( KCRUidBrowser );
        rep->Get( KBrowserDefaultCharset, iDefaultCharset );
        delete rep;
    }
}

我们还是挑一处重点代码 iWebKitView = CWebKitView::NewL(*iBrCtl, *this); 构造了一个CWebkitView,这个类的关系就比较复杂了:

class CWebKitView : public CEikBorderedControl,
                            private MPageScalerCallback,
                            public MAknPictographAnimatorCallBack,
                            private MImageDecodeListener

其两阶构造过程:

CWebKitView* CWebKitView::NewL(CCoeControl& aParent, CWebKitControl& aWebKitControl)
{
    CWebKitView* self = new (ELeave) CWebKitView( aWebKitControl );

    CleanupStack::PushL( self );
    self->ConstructL( aParent );
    CleanupStack::Pop(); // self

    return self;
}

void CWebKitView::ConstructL(CCoeControl& aParent)
{
    SetContainerWindowL(aParent);
    for( TInt i = 0;i < KZoomLevelCount;i++) {
         iZoomLevelArray.AppendL(KZoomLevels[i]);
    }

    // Create the KeyEventHandler
    iKeyEventHandler = CKeyEventHandler::NewL( *this );

    // Create the PointerEventHandler
    iPointerEventHandler = CPointerEventHandler::NewL( *this );

    // construct the main frame
    iMainFrame = CWebKitFrame::NewL( *this, 2, 2 );

    // render target
    if( iWebKitControl->BrCtl().Capabilities() & TBrCtlDefs::ECapabilityWebKitLite ) {
        iRenderTarget = &CStaticObjectContainer::Instance().SurfaceL(EColor64K);
        iIsPageScalerEnabled = EFalse;
        MemoryManager::SetRescueBufferSize( KRescueBufferSizeForLite );
    }
    else {
        iRenderTarget = &CStaticObjectContainer::Instance().SurfaceL(EColor16MU);

        // initialize page scaler in full version
        InitializePageScalerL();
        iIsPageScalerEnabled = ETrue;
        MemoryManager::SetRescueBufferSize( KRescueBufferSizeForFull );
    }

    // Create and offscreen device and context
    iBitmapDevice = CFbsBitmapDevice::NewL( iRenderTarget->OffscreenBitmap() );
    iWebCoreContext = CWebCoreGraphicsContext::NewL( iBitmapDevice, iRenderTarget->OffscreenBitmap(), iMainFrame );
    iRepaintTimer = CPeriodic::NewL( CActive::EPriorityHigh );

#if USE_DIRECT_SCREEN_ACCESS
    iDirectAccessScreenDevice = CFbsScreenDevice::NewL(_L("scdv"),EColor64K);
    User::LeaveIfError(iDirectAccessScreenDevice->CreateContext(iDirectAccessGc));
#endif //USE_DIRECT_SCREEN_ACCESS

    iInfoTextRenderer = CTextRendererFactory::InstanceL()->RendererWithFamilies(0,0,25,0);
    iInfoTextRenderer->Ref();

    // virtual cursor
    if ( iWebKitControl->BrCtl().Capabilities() & TBrCtlDefs::ECapabilityCursorNavigation) {
        iCursor = &CStaticObjectContainer::Instance().CursorL();
    }
    else {
        iTabbedNavigation = ETrue;
    }

    CImageRendererFactory::InstanceL()->AppendImageDecodeListener(*this);

    if (FeatureManager::FeatureSupported(KFeatureIdJapanesePicto)) {
      iPictographInterface = CAknPictographInterface::NewL( *this, *this );
    }
    else {
      iPictographInterface = NULL;
    }

    if (AknLayoutUtils::PenEnabled()) {
        DrawableWindow()->SetPointerGrab(ETrue);
        EnableDragEvents();
    }

    SetViewVisible(ETrue);
}

我们可以看到代码依据不同的能力创建了不同的surface(iRenderTarget),来看看创建surface的代码:

CWebKitSurface& CStaticObjectContainer::SurfaceL(TDisplayMode aMode)
{
    if ( !iWebKitSurface ) {
        // init offscreen surface
        iWebKitSurface = CWebKitSurface::NewL( aMode );
    }

    return *iWebKitSurface;
}

CWebKitSurface* CWebKitSurface::NewL(TDisplayMode aMode)
{
    CWebKitSurface* self = new (ELeave) CWebKitSurface(aMode);

    CleanupStack::PushL( self );
    self->ConstructL();
    CleanupStack::Pop( self );

    return self;
}

void CWebKitSurface::ConstructL()
{
    // Create a bitmap to be used offscreen
    iOffscreenBitmap = new (ELeave) CFbsBitmap();
    User::LeaveIfError( iOffscreenBitmap->Create( TSize(0,0),  iDisplayMode) );
}

嗯,到这里,一个被CWebKitSurface所封装的CFbsBitmap的实例被创建了出来。可以想象得到,该Bitmap作为一个buack buffer,WebKit将所有需要绘制的东西全部渲染到该buffer上,然后通过Symbian的GC刷到屏幕上。 

让我们回到CWebKitView,上面已经看到,这个Surface是被引用到了成员变量iRenderTarget。既然已经做了猜测,那我们来应征一下是不是这样做的:

void CWebKitView::Draw( const TRect& aRect ) const
{
    CWindowGc& gc = SystemGc();
    CEikBorderedControl::Draw( aRect );

#if USE_DIRECT_SCREEN_ACCESS
    // put offscreen bitmap onto the screen
    iDirectAccessGc->BitBlt( Rect().iTl, iRenderTarget->OffscreenBitmap() );
    TRegionFix<1> reg;
    reg.AddRect(Rect());
    iDirectAccessScreenDevice->Update(reg);
#else
    // put offscreen bitmap onto the screen
    iRenderTarget->Flip( Rect().iTl, gc );
    CWidgetExtension* wdgtExt = iWebKitControl->WidgetExtension();
    if (wdgtExt) {
        TWidgetRenderer* widgetRenderer = iWebKitControl->WidgetExtension()->WidgetRenderer();
        if (widgetRenderer && widgetRenderer->TransitionInProgress()) {
            widgetRenderer->DrawTransition(gc, OffscreenBitmap());
        }
    }

    if (iIsPageScalerEnabled) {
        iPageScaler->Draw( gc,aRect );
    

    if (iToolBar) {
        iToolBar->Draw(gc);
    }

    if (iPopupDrawer) {
        iPopupDrawer->DrawPopup();
    }

# if DRAW_DEBUG
   
# endif // DRAW_DEBUG
#endif // USE_DIRECT_SCREEN_ACCESS
}

好的,这里用宏 USE_DIRECT_SCREEN_ACCESS 来区分使用DSA和不使用DSA两种刷屏方式。那么这个Draw是何时被调用的呢?下面看看:

//-------------------------------------------------------------------------------
// ScheduleRepaint
// Schedule an asynchronous repaint
//-------------------------------------------------------------------------------
void CWebKitView::ScheduleRepaint( const TRect& aRect )
{
    iRepaintRegion.AddRect( aRect );

    // if we are active, we'll repaint when timer fires
    if ( !iRepaintTimer->IsActive() ) {
        // no timer yet, repaint immediatly (but asynchronously)
        iRepaintTimer->Start( 0, 0, TCallBack( &DoRepaintCb, this ) );
    }
}

//-------------------------------------------------------------------------------
// DoRepaintCb
// C-style TCallback function
//-------------------------------------------------------------------------------
TInt DoRepaintCb( TAny* aPtr )
{
    static_cast(aPtr)->DoRepaint();
    return EFalse;
}

//-------------------------------------------------------------------------------
// DoRepaint
// Perform a scheduled repaint
//-------------------------------------------------------------------------------
void CWebKitView::DoRepaint()
{
    // the timer must always be canceled here!
    iRepaintTimer->Cancel();

    // only repaint when this view owns the surface
    if ( iRenderTarget->CurrentView() != this ) return;

    // anything to do?
    if ( !iRepaintRegion.IsEmpty() ) {
        SyncRepaint( );
    }
}

//-------------------------------------------------------------------------------
// SyncRepaint
// Repaint the current repaint region synchronously and clear it
//-------------------------------------------------------------------------------
void CWebKitView::SyncRepaint()
{
    // only repaint when view owns the surface
    if( iRenderTarget->CurrentView() != this ) return;

    CFbsBitGc&gc = iWebCoreContext->Gc();
    gc.Reset();
    TRect rect(iRepaintRegion.BoundingRect());
    TBool needsDraw( EFalse );
    TRect viewRect( iMainFrame->VisibleRect() );

    // check if the are is in view
    if ( rect.Intersects( viewRect ) ) {
        // ensure all documents have valid layout
        iMainFrame->LayoutRecursive();

        // maybe it got bigger in layout?
        rect = iRepaintRegion.BoundingRect();
        TPoint p( iMainFrame->ContentPosition() );
        rect.Move(-p);
        gc.Clear( rect ); // TSW Id: SJUN-73XBVC : text appears overlapped when the document
                               // changes in onload since the OffScreenBitmap is not cleared.

        // draw to back buffer
        iMainFrame->Draw( *iWebCoreContext, rect );
        needsDraw = ETrue;
    }

    if (needsDraw) {
        // blit the whole backbuffer to the screen (in Draw())
        // This could be optimized further to blit only the affected area.
        // Not a big win, scrolling is the most critical case and there you
        // need to blit the whole buffer anyway.
        DrawNow();

        if (iWebKitControl->PageView()) {
            iWebKitControl->BrCtl().DrawNow();
        }
    }

    // dont do next async repaint until KRepaintDelay
    iRepaintRegion.Clear();
    iRepaintTimer->Cancel();
    TBool complete = iWebKitControl->IsProgressComplete() && CImageRendererFactory::Instance()->DecodeCount()==0;
    iRepaintTimer->Start(complete ? KRepaintDelayComplete : KRepaintDelayLoading, 0, TCallBack( &DoRepaintCb, this ) );
}

分析上面的代码得知是由一个计时器导致异步重绘。来看看计时器是何时被启动的:

//-------------------------------------------------------------------------------
// Called when decoding an image starts or completes
//-------------------------------------------------------------------------------
void CWebKitView::ProgressAndImageDecodeComplete()
{
    if (iWebKitControl->BrCtl().Capabilities() & TBrCtlDefs::ECapabilityGraphicalPage) {
        if( iPageScaler )
            iPageScaler->DocumentCompleted();
    }

    // Everything is ready, let's do a draw on the visible area
    // in case some image has not been drawn after finishing decoding
    ScheduleRepaint( iMainFrame->VisibleRect() );
    iWebKitControl->BrCtl().DrawScrollbarBackground();
}

//-------------------------------------------------------------------------------
// Called when decoding an image starts or completes
//-------------------------------------------------------------------------------
void CWebKitView::ImageDecodeEvent( TInt aImageCount, TBool aFinishedDecoding )
{
    if (aFinishedDecoding && iWebKitControl->BrCtl().Capabilities() & TBrCtlDefs::ECapabilityGraphicalPage) {
        TRAP_IGNORE( if( iPageScaler ) iPageScaler->DocumentChangedL() );
    }

    // don't send event until all images are decoded
    if (aImageCount==0 && iWebKitControl->IsProgressComplete()) {
        ProgressAndImageDecodeComplete();
    }
}

// -----------------------------------------------------------------------------
// CWebKitControl::FinalProgressComplete
//
// -----------------------------------------------------------------------------
void CWebKitControl::FinalProgressComplete( TInt aError )
{
   

    if (CImageRendererFactory::Instance()->DecodeCount()==0) {
        iWebKitView->ProgressAndImageDecodeComplete();
    }

    // suspend core timers if the application is in the
    // background
    if( iSuspendTimers ) {
        CWebCoreBridge::SetDefersTimers( ETrue );
        iWebKitView->MainFrame().WebKitBridge().WebCoreBridge().SetDeferAnimations();
    }
}

上面的代码显示在所有进度结束并且图片解码结束时会启用该计时器,这里的进度指的是Fetch。
初步的分析就到这里,后续文章将分析其文字绘制和控件绘制。