webkit之layout

http://blog.chinaunix.net/uid-580722-id-2090487.html

分类:

本文是在转载的文章基础上,增加了一些自己的见解。
WebKit
在渲染页面之前,需要确定各个元素的位置、大小,而这个过程就是layout(布局)。下面,我们对layout的主要过程进行一番说明。

一、FrameView::layout方法
FrameView
作为与View相关的类,其主要涉及与显示相关的内容,而其中对页面元素的布局至关重要,这也是浏览器的核心处理部分。

我们都知道浏览器从Web服务器获得数据后,经解析会构建DOM树、Render树,然后进行布局处理,进而为渲染页面作好准备,其中的布局处理往往由FrameView::layout方法发起,让我们来具体看看其实现,一窥其主要实现过程。

void FrameView::layout(boolallowSubtree)
{
    if
(m_midLayout)
      return
;

    
//Always ensure our styleinfois up-to-date.This can happen in situations where the layout beats any sort ofstylerecalc update that needs to occur.

    
//进行CSSStyleSelector的更新处理,因为一旦CSS发生变化,布局的结果也可能发生相关变化,所以在开始布局之前,需要检查CSS是否发生变化,如果有则需要作相应调整,进而可能影响Render树等。

    if
(m_frame->needsReapplyStyles())
       m_frame-
>reapplyStyles();
    elseif
(document->childNeedsStyleRecalc())
       document-
>recalcStyle();

    boolsubtree
=m_layoutRoot;
    RenderObject
*root =subtree ? m_layoutRoot : document->renderer();
    if
(!root){
        
//FIXME: Do we need to set m_sizehere?
        m_layoutSchedulingEnabled
=true;
        return
;
    
}

    
//布局的处理可能相互嵌套,这与发起布局处理的时机相关。
    
//设置滚动条相关
    if
(m_canHaveScrollbars){
        hMode
=ScrollbarAuto;
        vMode
=ScrollbarAuto;
    
}else {
        hMode
=ScrollbarAlwaysOff;
        vMode
=ScrollbarAlwaysOff;
    
}

    if
(!subtree){
        
//Now set our scrollbar state forthe layout.

        if
(body->hasTagName(framesetTag)&&!m_frame->settings()->frameFlatteningEnabled()){
            
//frameset而且设置了frameFlatteningEnabled,则关闭滚动条
                
body->renderer()->setChildNeedsLayout(true);
                vMode
=ScrollbarAlwaysOff;
                hMode
=ScrollbarAlwaysOff;
            
}else if (body->hasTagName(bodyTag)){
                
//设置滚动条
                applyOverflowToViewport
(o,hMode,vMode);
            
}
    
}

    
//root往往为RenderView对象
    RenderLayer
*layer =root->enclosingLayer();

    m_midLayout
=true;
    beginDeferredRepaints
();
    
root->layout();
    endDeferredRepaints
();
    m_midLayout
=false;

    m_layoutSchedulingEnabled
=true;

    if
(!subtree&&!static_cast(root)->printing())
        adjustViewSize
();

    
//Now update the positions of alllayers.
    
//对当前Render树布局完后,设置RenderLayer树的布局信息,其中m_doFullRepaint描述是否需要发起渲染处理。

   beginDeferredRepaints
();
   layer-
>updateLayerPositions(...);
   endDeferredRepaints
();

    
//设置快速blit
    setCanBlitOnScroll
(!useSlowRepaints());

    
//因为在布局的过程中,可能进一步获得网页数据,则需要继续布局处理。
    if
(!m_postLayoutTasksTimer.isActive()){
        
//CallsresumeScheduledEvents()
        performPostLayoutTasks
();

        if
(!m_postLayoutTasksTimer.isActive()&&needsLayout()){
            
//Post-layout widget updates or an event handler made us need layoutagain.Lay out again,but this time deferwidget updates and event dispatch until after wereturn.
            m_postLayoutTasksTimer
.startOneShot(0);
            pauseScheduledEvents
();
            layout
();
        
}
    
}else {
        resumeScheduledEvents
();
    
}
}





FrameView::layout
往往会调用Render树根的layout方法即RenderView::layout。

二、RenderView::layout方法

void RenderView::layout()
{
    if
(printing())
        m_minPrefWidth
=m_maxPrefWidth =m_width;

    
//Use calcWidth/Heightto get the new width/height,since this will take the full pagezoom factor into account.
    bool relayoutChildren
=!printing()&&(!m_frameView||m_width !=viewWidth()||m_height !=viewHeight());
    if
(relayoutChildren){

       setChildNeedsLayout(true, false);
       for (RenderObject* child = firstChild(); child; child =child->nextSibling()) {
           if (child->style()->height().isPercent() ||child->style()->minHeight().isPercent() ||child->style()->maxHeight().isPercent())
               child->setChildNeedsLayout(true, false);
       }

     }

    ASSERT
(!m_layoutState);
    LayoutState state
;
    
//FIXME: May be better to push aclip and avoid issuing offscreen repaints.
    state
.m_clipped=false;
    m_layoutState
=&state;

    if
(needsLayout())
        RenderBlock::layout
();//类继承的好处,直接调用父类的layout

   // Reset overflow and then replace it with docWidth anddocHeight.
    m_overflow.clear();
   addLayoutOverflow(IntRect(0, 0, docWidth(), docHeight()));

    ASSERT
(m_layoutStateDisableCount==0);
    ASSERT
(m_layoutState==&state);
    m_layoutState
=0;
    setNeedsLayout
(false);
}

voidRenderBlock::layout
()
{
    
//Update our first letter infonow.
    updateFirstLetter
();
    
//Tablecells call layoutBlock directly,so don't add any logichere. Put code into layoutBlock().
    
layoutBlock(false);

    // It'
ssafe to check forcontrol clip here,since controls can never be tablecells.If we have a lightweight clip, there can never be any overflowfrom children.
    if (hasControlClip() &&m_overflow)
       clearLayoutOverflow();
}



三、RenderBlock::layoutBlock方法

layoutBlock会分成两个分支:layoutInlineChildren和layoutBlockChildren

void RenderBlock::layoutBlock(boolrelayoutChildren)
{
   
...
   calcWidth
();//先计算宽度
   calcColumnWidth
();
   

   m_overflow.clear();   

    if (oldWidth!=width() ||oldColumnWidth !=desiredColumnWidth())
       relayoutChildren
=true;
   clearFloats
();

   int previousHeight
=height();
   setHeight(0);

    
//这就是在布局基本概念中提到的Block-level元素的子节点要么是Block-level元素要么为Inline-level元素。
    if
(childrenInline())
        
layoutInlineChildren(relayoutChildren,repaintTop,repaintBottom);
    else
        
layoutBlockChildren(relayoutChildren,maxFloatBottom);

    
//Expand our intrinsic heightto encompass floats.
    int toAdd
=borderBottom()+paddingBottom()+horizontalScrollbarHeight();
    if
(floatBottom()>(m_height- toAdd)&&(isInlineBlockOrInlineTable()||isFloatingOrPositioned()||hasOverflowClip()||
(parent()&&parent()->isFlexibleBox()||m_hasColumns)))
    setHeight(floatBottom
()+toAdd);

    
//Now lay out our columns within this intrinsic height,since they can slightly affect the intrinsic heightas we adjust forclean column breaks.
    int singleColumnBottom
=layoutColumns();

    
//Calculate our new height.//布局完子节点后确定父节点高度
    int oldHeight
=height();
    calcHeight
();

    if
(previousHeight!=height())
        relayoutChildren
=true;


    //另外布局属性为Fixed和absolute的元素
    layoutPositionedObjects
(relayoutChildren||isRoot());

     // Update ourscroll information if we're overflow:auto/scroll/hidden now thatwe know if we overflow or not.
   updateScrollInfoAfterLayout();

    // Repaint with ournew bounds if they are different from our old bounds.
   bool didFullRepaint = repainter.repaintAfterLayout();
   if (!didFullRepaint && repaintTop != repaintBottom &&(style()->visibility() == VISIBLE ||enclosingLayer()->hasVisibleContent())) {
       // 设置
repaintRect

       // Make sure the rect isstill non-empty after intersecting for overflow above
       if (!repaintRect.isEmpty()) {
           repaintRectangle(repaintRect); // We need to do a partialrepaint of our content.
           if (hasReflection())
               repaintRectangle(reflectedRect(repaintRect));
       }
    }
   setNeedsLayout(false);
}



四、RenderBlock::layoutBlockChildren方法


voidRenderBlock::layoutBlockChildren(boolrelayoutChildren,int&maxFloatBottom)
{
inttop
=borderTop()+paddingTop();
intbottom
=borderBottom()+paddingBottom()+horizontalScrollbarHeight();



    // Fieldsets needto find their legend and position it inside the border of theobject.
    // The legend then gets skippedduring normal layout.
    RenderObject* legend =layoutLegend(relayoutChildren);


//遍历子节点

RenderBox* next = firstChildBox();
   while (next) {
       RenderBox* child = next;
       next = child->nextSiblingBox();

       if (legend == child)
           continue; // Skip the legend, since it has already been positionedup in the fieldset's border.
      // Make sure we layout children if they need it.
       // FIXME: Technically percentage height objects only need arelayout if their percentage isn't going to be turned into
       // an auto value.  Add a method to determine this, so that wecan avoid the relayout.
       if (relayoutChildren || ((child->style()->height().isPercent()|| child->style()->minHeight().isPercent() ||child->style()->maxHeight().isPercent()) &&!isRenderView()))
           child->setChildNeedsLayout(true, false);

       // If relayoutChildren is set and we have percentage padding, wealso need to invalidate the child's pref widths.
       if (relayoutChildren &&(child->style()->paddingLeft().isPercent() ||child->style()->paddingRight().isPercent()))
           child->setPrefWidthsDirty(true, false);

       // Handle the four types of special elements first.  Theseinclude positioned content, floating content, compacts and
       // run-ins.  When we encounter these four types of objects,we don't actually lay them out as normal flow blocks.
       if (handleSpecialChild(child, marginInfo))
           continue;

        // Layout the child.
       
layoutBlockChild(child,marginInfo, previousFloatBottom, maxFloatBottom);
   }

    // Now do the handling of the bottomof the block, adding in our bottom border/padding and
   // determining the correct collapsed bottom margininformation.
    handleBottomOfBlock(top,bottom, marginInfo);
}


layoutBlockChild方法就比较复杂了,具体可以自己看代码。

五、RenderBlock::layoutInlineChildren方法
这个方法相当复杂,其作用就是布局文字、图像等,对文字行高确定、断行等处理,同时还包括文字从左到右或从右到左的布局处理。具体可以参考bidi.cpp中的源码实现。

六、调用FrameView::layout方法的时机
由于从Web服务器获取的网页数据不可能一次性完成,往往需要边获取数据,边布局,然后渲染,这样才可能获得良好的用户感受。

所以一旦获得主要数据如css数据及body等标签后,就可以开始布局,布局完后会根据当前条件决定是否将布局的数据渲染出来,或者继续布局处理后来获取的数据,这样也增加了布局处理过程的复杂度。

而调用layout方法的时机也至关重要,因为layout本身就可能需要花费大量的时间如layoutBlockChildren、layoutInlineChildren等处理,其往往与网页的内容有关,而网页的内容却由网页开发者来确定,对浏览器来讲是千变万化的,这就对layout方法的实现及调用时机提出更高的要求,同时确定了其复杂性。

调用layout的时机主要有获得一定DOM文档数据后调用Document::updateLayout()、需要重新使用CSS数据时调用Document::recalcStyle()、改变窗口大小后调用Frame::forceLayout()等来实现。。。

七、总结
其实WebKit涉及网页布局方面的layout方法蛮复杂的,如其他RenderObject子类也会根据自身情况重载实现layout,还有对float、fixed、absolute、inline元素等的处理,但其主要逻辑就象上面所提,这里只是汇总一下主要流程及概念,针对每一个具体标签或RenderObject的布局实现则需要更深一步的了解,希望大家能对了解WebKit的网页布局过程有一个清晰而准确的认识。。








具体的layout逻辑流程?原理?

看下面的webkit官方的关于webcorerenderinig的资料:

https://www.webkit.org/blog/114/webcore-rendering-i-the-basics/

https://www.webkit.org/blog/115/webcore-rendering-ii-blocks-and-inlines/

https://www.webkit.org/blog/116/webcore-rendering-iii-layout-basics/

https://www.webkit.org/blog/117/webcore-rendering-iv-absolutefixed-and-relative-positioning/

https://www.webkit.org/blog/118/webcore-rendering-v-floats/



机器人中的数值优化(十五)——PHR增广拉格朗日乘子法 本系列文章主要是我在学习《数值优化》过程中的一些笔记和相关思考,主要的学习资料是深蓝学院的课程《机器人中的数值优化》和高立编著的《数值最优化方法》等,本系列文章篇数较多,不定期更新,上半部分介绍无约束优化,下半部分介绍带约束的优化,中间会穿插一些路径规划方面的应用实例 阅读详情

相关推荐

python数据处理与分析案例,python如何进行数据处理

大家好,本文将围绕基于python的数据处理案例展开说明,python数据处理与分析案例是一个很多人都想弄明白的事情,想搞清楚python如何进行数据处理需要先了解以下几个事情。大家好,小编来为大家解答以下问题,python数据处理案例步骤及其代码,python数据处理与分析教程,今天让我们一起来看看吧!哈喽~今天学习记录的是数据分析实训小案例。就用这个案例来好好巩固一下 python 数据分析三剑客。

a17348的博客 1240

WebKit之布局(layout)

WebKit之布局(layout) WebKit 在渲染页面之前,需要确定各个元素的位置、大小,而这个过程就是layout(布局)。下面,我们对layout的主要过程进行一番说明。 一、FrameView::layout方法 FrameView作为与View相关的类,其主要涉及与显示相关的内容,而其中对页面元素的布局至关重要,这也是浏览器的核心处理部分。 我们

xiaoliangxixi的专栏 612

Zemax热分析实战:5分钟搞定双胶合透镜温度漂移优化(附操作数详解)

本文详细解析了使用Zemax进行光学系统热分析的全流程,以双胶合透镜为例,重点阐述了如何通过多重结构编辑器模拟温度变化,并利用关键操作数优化系统以抵抗温度漂移。文章提供了从基础设置到高级优化的实战技巧,帮助工程师快速掌握无热化设计方法,确保系统在宽温范围内保持稳定成像性能。

n4o5p6q7r的博客 399

浏览器探究——webkit部分——Button

浏览器探究——webkit部分——Button 测试页面 <inputtype="submit"value="GOOD"style="height:30px;width:70px;"> DOM Tree *#document0xcfbbf8 HTML0xc7dba8 HEAD0xcef368 BODY0xcd4aa8 INPUT0x9100b8STYLE=hei...

whotodo 216

本体研究综述(转)

(这篇文章是从http://imarine.blog.163.com/中转载过来的,个人觉得对本体的介绍比较全面!)本体综述本体作为一种能在语义和知识层次上描述领域概念的建模工具,其目标是捕获相关领域的知识,确定该领域内共同认可的词汇,通过概念之间的关系来描述概念的语义,提供对该领域知识的共同理解。语义Web(the Semantic Web)是在本体理论基础之上对现有Web所进行的扩展[

zhenshideni611的专栏 1838

记一次完整的RPG Maker MV游戏逆向过程(思路一)

了解RPG Maker MV的文件建构 上一篇文章,我们已经成功在PC上运行了游戏,那我们如何对游戏进行逆向呢? 首先要了解正常的RPG Maker MV制作的游戏应该具有哪些文件,以及他的结构 那如何了解他的结构呢,很简单,我们只需要用RPG Maker MV创建一个默认工程,来看看一个游戏的最简结构是怎么样的 创建新项目 项目创建完成 查看项目目录结构 是不是和之前解包出来的很像呢? 通...

Ciiiiiing的博客 1万+

FrameView::layout干了什么

FrameView::performLayout -》如果该FrameView对应的是Document的mainframe, 那么就取出其RenderView,并调用RenderView::layout(),如果不是mainframe, 那么就取出subtree root, 我理解应该是renderobject tree中某个子树layout,我们不可能每次都layout整棵树。 Ren

484

癌症免疫治疗预测神器TIDE:从原理到实战(避坑指南)

本文详细解析了癌症免疫治疗预测工具TIDE的核心原理及实战应用。TIDE算法通过模拟肿瘤免疫逃逸的两种核心机制(T细胞功能障碍和排除),为临床决策提供精准预测。文章涵盖数据准备、标准化流程、网页工具操作指南及结果解读,帮助科研人员和临床医生避开常见误区,优化免疫治疗策略。

weixin_29309475的博客 200

WebKit之layout

本文是在转载的文章基础上,增加了一些自己的见解。 WebKit 在渲染页面之前,需要确定各个元素的位置、大小,而这个过程就是layout(布局)。下面,我们对layout的主要过程进行一番说明。 一、FrameView::layout方法 FrameView作为与View相关的类,其主要涉及与显示相关的内容,而其中对页面元素的布局至关重要,这也是浏览器的核心处理部分。 我们都知道浏

Android WebKit 1621

KEIL 5.38如何手动安装ARM Compiler V5?完整配置流程分享

本文详细介绍了在KEIL 5.38环境下手动安装和配置ARM Compiler V5(AC5)的完整流程,解决历史遗留项目的兼容性问题。从获取安装包、环境变量设置到KEIL集成配置,逐步指导开发者完成AC5的部署,并提供了常见错误排查和性能优化建议,帮助开发者高效维护基于C编译器的老项目。

weixin_29168813的博客 629

Open3D 计算点云的倒角距离(Chamfer Distance)【2025最新版】

;Chamfer Distance距离可以计算生成点云数据与标签点云数据之间的平均最短点距离。Open3D可以直接用来计算点云的Chamfer Distance距离。博客长期更新,本文最近一次更新时间为:2025年2月18日。

点云侠的博客 3254

【-】WebKit Layout (布局)

WebKit 在渲染页面之前,需要确定各个元素的位置、大小,而这个过程就是layout(布局)。下面,我们对layout的主要过程进行一番说明。 一、FrameView::layout方法 FrameView作为与View相关的类,其主要涉及与显示相关的内容,而其中对页面元素的布局至关重要,这也是浏览器的核心处理部分。 我们都知道浏览器从Web服务器获得数据后,经解析会构建DOM树、

tuhuolong的专栏 5187

YOLOv11改进 | Neck篇 | 独创HFPN利用分层特征融合块HFFB模块融合多层次特征改进yolov11(全网独家创新)

本文给大家带来的最新改进是利用分层特征融合块HFFB创新yolov11的neck部分我称之为HFPN,这个模块可以融合局部特征、全局特征、中间特征将三种特征融合在一起辅助yolov11进行检测,经过我的设计分为三种可以针对大目标、小目标、标准目标的检测方式均不同,大家可以根据自己的数据集进行不同的选择,本文的内容为我独家创新。YOLOv11改进系列专栏——本专栏持续复习各种顶会内容——科研必备目录一、本文介绍二、原理介绍三、核心代码四、添加方法4.1 修改一4.2 修改二4.3 修改三4.4 修改四。

Snu77的博客 5218

Block结点垂直排列,Inline结点水平排列 (WebKit Layout)

tuhuolong的专栏 691

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

WebKit Layout (布局)

WebKit 在渲染页面之前,需要确定各个元素的位置、大小,而这个过程就是layout(布局)。下面,我们对layout的主要过程进行一番说明。 一、FrameView::layout方法 FrameView作为与View相关的类,其主要涉及与显示相关的内容,而其中对页面元素的布局至关重要,这也是浏览器的核心处理部分。 我们都知道浏览器从Web服务器获得数据后,经解析会构建DOM树、

791

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

android 4.0.1 webkit layout 过程分析

当RenderObject被创建并加入到RenderTree后,他们还没有获得自身的大小和位置信息。 各个具体的RenderBoxModelObject子类确定起自身的位置和大小的过程成为Layout. layout过程执行完后,RenderTree上每个节点的位置信息和自身大小就确定下来了。 比如RenderBox的表示位置信息和自身大小的成员变量:  IntRect m_frameRe

jaylinzhou的专栏 1727

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

WebKit 布局Layout

转:http://blog.youkuaiyun.com/milado_nju/article/details/7854312 # WebKit布局(layout)基础 ## 概述 一个网页从文本信息到最后的渲染结果,要经过很多复杂的过程,前面介绍过DOM树、Render树的创建,也阐述了页面如果被渲染的,其实,这两者中间还有一个非常重要的步骤――布局计算,这是因为在渲染每个元素之前,渲染引

Super爬爬虫 2030

深入分析WebKit之Layout Tests

转载请注明出处:http://blog.youkuaiyun.com/horkychen 来自内部一个分享PPT整理过程的知识点,没有特别组织。   Layout Test主流程: 运行的指令:   run-webkit-tests [选项] 测试脚本文件或所在的目录     主要的参数有:         --verbose  显示详细的信息         --no-bui

求索 6683

Webkit之理解Layout

一个网页从文本信息到最后的渲染结果,要经过很多复杂的过程,前面介绍过DOM树、Render树的创建,也阐述了页面如果被渲染的,其实,这两者中间还有一个非常重要的步骤――布局计算,这是因为在渲染每个元素之前,渲染引擎必须知道它的位置大小等布局信息,我们把计算这些信息的过程称之为布局。 布局根据其计算的范围大致可以分为两类,第一类是对整个Render树进行的计算,第二类是对Render树中某个子

484

理解WebKit和Chromium: WebKit布局 (Layout)

转载请注明原文地址:http://blog.youkuaiyun.com/milado_nju/article/details/7854312 # WebKit布局(layout)基础 ## 概述 一个网页从文本信息到最后的渲染结果,要经过很多复杂的过程,前面介绍过DOM树、Render树的创建,也阐述了页面如果被渲染的,其实,这两者中间还有一个非常重要的步骤――布局计算,这是因为在渲染每个元素之前,渲...

923723914 370

WebKit Layout 数据结构

一、Render树的构成 在我们编写网页及使用JS的时候,大概都知道DOM树及其主要构成,了解到DOM树的构建其实质是对一个html或xml文件的内容采取树结构的方式来组织及描述,不同的标签及其在文档中的位置决定了其在整颗DOM树的地位及属性,针对具体DOM树的构成及不同树节点的描述,可以参考有关DOM的相关标准等,以后有机会我们也会单独来了解。 也许对于Render树大家就不那么了解了,

tuhuolong的专栏 840

Webkit Layout Test理论部分1 + 深入分析部分2– Layout Test :

原文 : http://blog.youkuaiyun.com/yajun0601/article/details/8887332   当我开始做 WebKit 开发的时候,令我好奇的一件事儿就是这玩艺儿怎么测试。作为一个 Web 开发者,我清楚浏览器渲染引擎有多少 bug (尽管现在情况好了很多),以及日益复杂的web 页面给浏览器引擎带来多大的挑战。伴随bug 一起工作多年自然是要极力避免的事情,所以

moubenmao的专栏 1896

Writing Layout Tests for DumpRenderTree – WebKit

Writing Layout Tests for DumpRenderTree – WebKit Writing Layout Tests for DumpRenderTree – WebKitWriting good test cases ¶General tip...

177

webkit chromium layout render继承结构

LayoutObject LayoutText LayoutBR LayoutCounter LayoutTextCombine LayoutTextFragment LayoutWordBreak LayoutBoxModelObject LayoutInline LayoutRubyAsInline LayoutQuote LayoutBox ...

marvel_bin的博客 427
上一篇: FrameView::layout干了什么
下一篇: blink渲染知识(二) - webcore rendering basics
csshell2002
博客等级 码龄11年 30粉丝 41原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值