WebKit浏览器内核源码分析-HTML解析模型图
摘要:
通过分析WebKit的源代码,试图分析WebKit的内核设计架构,模块之间的关系,分析的时候以Qt的移植为参考,涉及移植的东西不多,主要还是以内核为主。在分析内核的时候,Frame是首当其冲的一个类,本文将分析Frame类的代码。
浏览器的请求一般是以页面请求为单位,当用户通过网址输入一个URL,浏览器就开始一个页面请求。而一个页面请求可能包含一到多个页面子帧,以及图片、CSS和插件等派生子资源。Page类就是用来对应这样的页面请求。Page类是WebKit中非常重要的类,它就像内核对外的一个聚合器。
1. 浏览器的请求概述
浏览器的请求一般是以页面请求为单位,当用户通过网址输入一个URL,浏览器就开始一个页面请求。而一个页面请求可能包含有一到多个子帧,以及图片、CSS和插件等派生子资源。Page类就是用来对应这样的页面请求。前进后退,导航,编辑,右键菜单,设置,Inspector等这些用户参与的动作,大部分是同Page相关的。而标记语言解析、排版、加载则更多的同Frame相关
我们通过几个图来看Qt移植中Page类同应用之间的关系。
QWebPage通过QWebPagePrivate维护Page类的指针,并在QWebPagePrivate的构造函数中实例化Page对象。QWebPage类通过之后的createMainFrame调用实例化QWebFrame,而在QWebFrameData的构造函数中,以Page指针为参数调用了 Frame::create创建出 Frame对象
1. 描述
Frame类是WebCore内核同应用之间联系的一个重要的类。它有点像设计模式中的Fa?ade,将内核的各个不同的零配件组装在了一起,但又不是Fa?ade,因为用户很多时候还是要直接去操作里面的组件。除了设计上的考虑,Frame还有语法上的意义,它对应于Page里面的帧。
2. 类结构
1) FrameTree对象用来协助管理父帧和子帧的关系,常见的比如main frame之中有iframe元素,就会调用FrameLoaderClientQt::createFrame来产生子帧,产生的子帧会通过appendChild添加到主帧的树状结构中。Frame通过FrameTree对象,可以方便地访问它的父帧,子帧,兄弟帧。
2) 维护FrameLoader对象用来完成frame的加载,FrameLoader是一个非常重要的类,后续进行进一步的分析。
3) 维护NavigationScheduler对象用来管理页面跳转调度(比如重定向,meta refresh等)。
4) DOMWindow用来管理同DOM相关的事件、属性和消息。
5) FrameViwe类用于Frame的排版。
6) Frame文档解析后,对每一个tag或者attr,会有对应的dom节点关联,Document类用来管理这些dom节点。不同的文档类型继承出不同的子类,比如HTML文档对应子类HTMLDocument,XML文档对应于XMLDocument。
7) SciptController对象,脚本控制器,用来管理脚本的执行和操作。
8) Editor对象用来处理页面的编辑相关的操作,比如拷贝,粘贴,输入等,Editor对象,它同Page类的EditorClient对象紧密合作。和EditorClient的关系就如同Page同Frame的关系。
9) SelectionController用来管理Frame中的选取操作。
10) AnimationControlle,动画控制,控制动画的播放,暂停,继续(同HTML video标签是否有关系?)
11) EventHandler,事件处理对象,这里的对象主要是同上层应用也就是用户参与的事件,比如鼠标事件,按键事件(快捷键等),滚动事件,resize事件等。这是一个浏览器外壳经常需要打交道的类。
3. 主要接口
3.1 Create
static PassRefPtr<Frame> create(Page*,HTMLFrameOwnerElement*,FrameLoaderClient*)
描述: 调用Frame构造函数,创建出Frame对象。有两个地方会创建Frame对象,一是要加载一个新的页面请求,这个时候会创建main frame,一是在加载子帧的时候,通过FrameLoaderClientQt的createFrame接口,创建子帧对应的Frame对象,在第一种情况中,HTMLFrameOwnerElement参数为NULL,第二种情况传子帧的父元素。在一个tab页内,main frame会重用。
调用系列:
àQwebPage::setView
àQwebPage::setViewportSize
àQwebPage::mainFrame
àQwebPagePrivate::createMainFrame
àQwebFrameData::QwebFrameData
àFrame::create
àFrameLoader::finishedLoading
à……
àHTMLDocumentParser::append
à……
àHTMLTreeBuilder::processToken
à……
àHTMLElementBase::openURL
àSubFrameLoader::requestFrame
à……
àFrameLoaderClientQt::creatFrame
àQwebFrameData::QwebFrameData
àFrame::create
3.2 createView
void createView(const IntSize&, const Color&, bool, const IntSize&, bool,
ScrollbarMode = ScrollbarAuto, bool horizontalLock = false,
ScrollbarMode = ScrollbarAuto, bool verticalLock = false)
描述:创建出FrameView对象,以用于之后的排版。应用调用这个函数的时候需要传入同排版有关的一些信息,如初始视窗大小,背景色,滚动条模式等。创建出FrameView以后,即调用Frame::setView设置成当前的FrameView。
函数调用系列:
àFrameLoader::commitProvisionalLoad
àFrameLoader::transitionToCommitted
àFrameLoaderClientQt::transitionToCommittedForNewPage
àFrame::createView
3.3 setDocument
void setDocument(PassRefPtr<Document>)
描述:设置同Frame关联的Document对象(一般是DocumentWriter创建的)。
函数调用系列:
àQWebFrame::QwebFrame
àQwebFramePrivate::init
àFrame::init
àFrameLoader::init
àDocumentWriter::begin
àFrame::setDocument
àDocumentLoader::receivedData
àDocumentLoader::commitLoad
àFrameLoaderClientQt::committedLoad
àDocumentLoader::commitData
àDocumentWriter::setEncoding
àDocumentWriter::willSetEncoding
àFrameLoader::receivedFirstData
àDocumentWriter::begin
àFrameLoader::clear
àFrame::setDocument
3.4 init
void Frame::init
描述:Frame对象初始化,会调用FrameLoader::init初始化FrameLoader对象。
调用系列:
àQWebFrame::QWebFrame
àQwebFramePrivate::init
àFrame::init
3.5 setPageAndTextZoomFactors
void setPageAndTextZoomFactors(float pageZoomFactor, float textZoomFactor)
描述:设置页面放大因子和文字放大因子。在网页缩放或者改变网页字体大小的时候调用。
WebKit内核源代码分析(二)
版权
摘要:本系列通过分析WebKit的源代码,试图分析WebKit的内核设计架构,模块之间的关系,分析的时候以Qt的移植为参考,涉及移植的东西不多,主要还是以内核为主。FrameLoader类负责一个Frame的加载,在Frame的流程中起到非常重要的重要,同很多组件都有交互,本文将分析FrameLoader类的代码。
1. 概述
顾名思义,FrameLoader是一个Frame的loader,它的作用就是为客户提供一个下载一个Frame的一系列接口。这里的客户指的是类的客户,比如Frame类,间接客户是上层应用,比如qwebframe。
从它的定义看,最容易想到的是一个load接口,用来将一个frame load下来。任何一个页面至少都需要一个mainframe,因此一个页面的下载一般就是从load一个mainframe开始。
在load frame的过程中,通过FrameLoaderClient接口将load过程的不同阶段告知客户。
FrameLoader通过setDocumentLoader相当于把load的工作委托给了DocumentLoader类。
FrameLoader同DocumentLoader是has-a的关系。一般在load的时候创建DocumentLoader。Frame调用DocumentLoader的startLoadingMainResource开始load frame。
2. 类关系
[img]http://dl.iteye.com/upload/attachment/437204/38586d86-f8fa-3023-b30f-e25e2e2a640a.jpg[/img]
1)Frame和FrameLoader是contain-a的关系,在Frame的构造函数中调用FrameLoader的构造函数,调用时传入参数Frame指针和FrameLoaderClient指针。
2)Frame有可能有子Frame,所以维护SubFrameLoader对象m_subframeLoader来管理子Frame的load。Frame可以对应xml document,也可对应html document,等等。跟Document相关的子resource的load不在FrameLoader的职责范围内。
3)包含一个DocumentWriter类对象m_writer,当Frame的数据load finish的时候,将数据传给DocumentWriter类,进行下一步的处理(比如解码)
4)FrameLoader维护了三个DocumentLoader对象,分别对应于不同的阶段,m_policyDocumentLoader对应于收到用户load调用,进行policy check阶段,m_provisionalDocumentLoader对应于policy check通过以后,Frame数据还没有到来之前,它会负责startLoadingMainResource的调用。m_documentLoader则是Frame第一个数据到来以后使用的DocumentLoader,这个时候,前一个主Frame的DocumentLoader已经不能再用(user agent开始白屏,刷掉前一个页面的显示)。
5)包含一个HistoryController对象,用于操作历史记录相关的接口,保存或者恢复Document和View相关的状态,维护前进后退队列,以实现前进后退功能,前进后退本质上是同Page对象关联的,FrameLoader通过HistoryController操作m_backFowardController对象
6)包含一个ResourceLoadNotifier对象,主要用于同ResourceLoader及FrameLoaderClient打交道,可以理解为ResourceLoader有事件变化或者发生的时候,通知FrameLoader的一个手段
7)包含一个SubframeLoader对象,当FrameLoader下载的Document有子帧需要请求的时候(比如HTMLDocument中解析到iframe 元素),用来处理子帧请求
8)将FrameLoader的状态封装到FrameLoaderStateMachine中,这个状态同FrameState不同,FrameState倾向于判断Frame涉及的Document的下载状态,是出于发起状态(Provisional),还是出于已经收到响应但不全(CommittedPage),还是响应收全的状态,倾向于同http相关。而FramLoaderStateMachine倾向于同DocumentLoader相关,用来描述FrameLoader处理DocumentLoader的节点,是处于已经创建,还是显示的状态。
9)PolicyChecker主要用来对FrameLoader进行一些校验。包括三种校验:NewWindow,Navigation和Content。NewWindow对应于浏览器需要新开一个tab页或窗口的时候,Navigation对应于一个页面请求发起的时候,Content校验对应于收到数据以后(判断Mime type等),PolicyChecker通过提供对应的接口,由FrameLoaderClient来对这些请求进行校验,以确定是否允许继续,或者需要其它的动作。
3. 主要接口
Frame::init
功能:FrameLoader的初始化
函数调用系列
àQWebFrame::QWebFrame(QwebPage* parent,QWebFrameData *frameData)
àQWebFramePrivate::init(QWebFrame* qwebframe,QWebFrameData* frameData)
àFrame::init()
àFrameLoader::init()
说明:主要做一些自身的初始化工作,比如初始化状态机,Sandbox Flags,创建DocumentLoader被设置为Policy DocumentLoader和Provisional DocumentLoader,调用DocumentLoader和documentWriter等的接口进行初始化操作
FrameLoader::commitProvisionalLoad
功能:提交Provisional阶段下载的数据
函数调用系列:
àDocumentLoader::finishLoading
àDocumentLoader::commitIfReady
àFrameLoader::commitProvisionalLoad
或者
àResourceLoader::didReceiveData
àMainResourceLoader::addData
àDocumentLoader::receiveData
àDocumentLoader::commitLoad
àDocumentLoader::commitIfReady
àDocumentLoader::commitProvisionalLoad
说明:这个接口主要的操作是将Provisional DocumentLoader设置成DocumentLoader,因为已经收到数据,所以FrameState也会跃迁到FrameStateCommittedPage。还有历史记录,PageCache相关的操作。另外,这个接口会间接调用FrameLoaderClientQt::transitionToCommittedForNewPage,通过Frame::createView创建出FrameView来。
Frame::finishedLoading
功能:frame请求网络加载完成的时候调用此接口
函数调用系列
àResourceLoader::didFinishLoading
àMainResourceLoader::didFinishLoading
àFrameLoader::finishedLoading
àFrameLoader::init()
说明:检查是否有网络错误,告诉DocumentLoader和DocumentWriter下载完成,以便进行后续操作(提交数据,解析)。
FrameLoader::finishedParsing
功能:解析完成调用此接口
函数调用系列
àDocumentWritter::end
à….
àDocument::finishParsing
à….
àDocument::finishedParsing
àFrameLoader::finishedParsing
FrameLoader::load(const ResourceRequest& request,bool lockHistory)
功能:加载一个frame请求,Frame请求相关的数据,封装成ResourceRequest传入。
函数调用系列:一般由应用触发调用
说明:这个接口调用FrameLoaderClientQt::createDocumentLoader创建出DocumentLoader,并以此DocumentLoader为Policy Document Loader,进入Policy check流程。
WebKit内核源代码分析(三)浏览器的请求
版权
摘要:浏览器的请求一般是以页面请求为单位,当用户通过网址栏输入一个url,浏览器就开始一个页面请求。而一个页面请求可能包含有一到多个页面子帧,以及图片、CSS和插件等派生子资源。Page类就是用来对应这样的页面请求。Page类是WebKit中非常重要的一个类,它就像内核对外的一个聚合器。
关键词:WebKit内核源代码,WebCore,Page,Frame,WebKit架构
1. 概述
浏览器的请求一般是以页面请求为单位,当用户通过网址栏输入一个url,浏览器就开始一个页面请求。而一个页面请求可能包含有一到多个页面子帧,以及图片、CSS和插件等派生子资源。Page类就是用来对应这样的页面请求。前进后退,导航,编辑,右键菜单,设置,Inspector等这些用户参与的动作,大部分是同Page相关的。而标记语言解析、排版、加载则更多地同Frame相关。
我们通过几个图来看下Qt移植中Page类同应用之间的关系。
QWebPage通过QWebPagePrivate维护Page类的指针,并在QWebPagePrivate的构造函数中实例化Page对象。QWebPage类通过之后的createMainFrame调用实例化QwebFrame,而在QwebFrameData的构造函数中,以Page指针为参数调用了Frame::create 创建出Frame对象。
Page类通过组合其它类的方式,实现了很多功能,Page类本身并没有多少代码。
2. 类关系
2.1 PageGroup
PageGroup并不是用来对Page进行管理的,而是设计用来将一些具有共同的属性或者设置的Page编成组的,以方便对这些属性进行管理。目前这样的属性包括localStorage的属性,IndexDB,User Script,User StyleSheet等。最常见的同PageGroup相关的操作是维护已访问链接(如addVisitedLink等接口)。根据地瓜的理解,假设WebKit内核之上架设多个应用(浏览器是一个应用),比较可能的是,一个应用独立一个PageGroup。这里同多tab页没有关系,多tab页属于同一个PageGroup。地瓜曾在mailing group上就这个问题咨询过,一位RIM的同学给我举了一个例子,比如一个基于WebKit的邮件程序,一方面他可能调用基于webkit的browser来显示网页,另外他本身也基于webkit来显示一些邮件,这两个之间的setting有很大可能不一样,他们就使用不同的PageGroup。
PageGroup中有这个Group已经安装并且使用的User Script和User StyleSheet的集合,一般在网页解析完毕后,这些User Script和User StyleSheet会插入到Document中。
PageGroup中还维护了Local Storage和Index DB相关的设置,比如它们的Path,上限等,通过GroupSettings类实现。
PageGroup创建以后,每次创建一个新的Page对象,会通过addPage接口加入到这个PageGroup的m_pages中。
每次有导航行为发生的时候,会调用addVisitedLink来将url加入到已访问链接中。如果浏览器要跟踪已访问的接口,则在初始化的时候必须调用PageGroup::setShouldTrackVisitedLinks,且参数为true。此处shouldTrackVisitedLinks是一个静态的全局变量,也就是说,所有应用维护一样的行为(一个应用将其设置为false会影响到其它同样基于此核的应用)?
Page类中维护了PageGroup的指针,并提供了group接口,这是个lazy接口,如果m_group不存在,会调用InitGroup来创建一个。对于Page类来说,如果没有设置GroupName,则在初始化的时候会生成一个空GroupName的PageGroup,由m_singlePageGroup维护,并把指针赋给m_group,如果以非空的名字调用了setGroupName,则会重新创建PageGroup,此时这个PageGroup由m_group来维护。
2.2 Setting
WebCore中的设置相关的类,浏览器应用的不少配置、选项同该类相关,Qt移植中,应用在创建Page对象后,会根据Page::settings来实例化QwebSetting。
2.3 Chrome
原生窗口接口类,参考地瓜写的”WebKit中的Chrome和ChromeClient”。
2.4 其它
SelectionController :负责管理Page中的选取操作,绝大部分选取操作是基于Frame的,只在Frame的Selection为空的时候,对焦点游标的绘制工作才会使用到Page类的SelectionController。
SharedGraphicsContext3D:共享3D图形上下文,为了优化2D显示而加入。在加速的2D canvas中,引入的DrawingBuffer的概念,SharedGraphicsContext3D提供了createDrawingBuffer来创建DrawingBuffer。
DragController:拖拽控制器。Chrome的超级拖拽功能同这个有关?地瓜会在以后对此进行求证。
FocusController:焦点控制器。考虑到焦点会在各个frame之间切换,所以由Page类维护焦点控制器最合适不过。
ContextMenuController:右键下拉菜单控制器。
InspectorController:Inspector控制器,浏览器中的很多开发工具都同这个类相关。
GeolocationController:定位定位服务控制器。
DeviceMotionController:设备移动控制器
DeviceOrientationController:设备方向控制器
SpeechInputClient:语音输入Client。
ProgressTracker:进度跟踪。
BackForwardController:前进后退操作控制。
Frame:一个Page由至少一个主帧和若干个其它子帧构成。
HistoryItem:历史记录。
PluginData:插件相关,未来可能同PluginDatabase类合并。主要是初始化Plugin的信息。
PluginHalter:用来控制Plugin的停止和重新开始。
RenderTheme:这个类提供了控件的渲染和绘制接口。Qt移植中,RenderThemeQt是RenderTheme接口的具体实现。
EditorClient:同编辑功能相关,比如拷贝、剪切、删除等操
WebKit 内核源代码分析 ( 四 )
摘要:本文介绍 WebCore 中 Loader 模块是如何加载资源的,分主资源和派生资源分析loader 模块的类关系。
WebKit 内核源代码分析 HTML解析模型
关键词: WebKit,Loader,Network,ResouceLoader,SubresourceLoader
一、类结构及接口
Loader 模块是 Network 模块的客户。 Network 模块提供指定资源的获取和上传功能,获取的资源可能来自网络、本地文件或者缓存。对不同 HTTP 实现的适配会在Network 层完成,所以 Loader 接触到的基本上是同 OS 和 HTTP 实现无关的 Network 层接口。
如上是 Loader 和 NetWork 之间的类关系图, ResourceHandleClient 是ResourceHandle 的客户,它定义一系列虚函数,这些虚函数是 ResouceHandle 的回调,继承类实现这些接口。
ResouceHandleClient 的接口同网络传输过程息息相关,一般为某一个网络事件对应的回调。下面是其中的一些接口。
// 一般情况下,在发起网络请求前调用,可以设置特定的 Http
头部,比如 user agent 等,在重定向请求的时候,也会自动调
用
void willSendRequest(ResourceHandle*, ResourceRequest&, const
ResourceResponse&)
// 上传数据的时候,在 TCP wrtie 事件的时候,向对方发送数据的
时候调用, loader 可以根据这个回调显示上传进度。
void didSendData(ResourceHandle*, unsigned long long
/*bytesSent*/, unsigned long long /*totalBytesToBeSent*/)
// 收到第一个响应包,此时至少 http 的部分头部已经解析(如
status code ), loader 根据响应的头部信息判断请求是否成功
等。
void didReceiveResponse(ResourceHandle*,
const ResourceResponse&)
// 收到 HTTP 响应数据,类似 tcp 的 read 事件,来 http 响应数据
了, Network 的设计机制是来一段数据上传一段数据。
void didReceiveData(ResourceHandle*, const char*, int,
int /*lengthReceived*/)
// 加载完成,数据来齐。
void didFinishLoading(ResourceHandle*, double /*finishTime*/)
// 加载失败
void didFail(ResourceHandle*, const ResourceError&)
// 要求用户鉴权
void didReceiveAuthenticationChallenge(ResourceHandle*,
const AuthenticationChallenge&)
WebCore 把要加载的资源分成两类,一类是主资源,比如 HTML 页面,或者下载项,一类是派生资源,比如 HTML 页面中内嵌的图片或者脚本链接。这两类资源对于回调的处理有很大的不同,比如,同样是下载失败,主资源可能需要向用户报错,派生资源比如页面中的一张图下载失败,可能就是图不显示或者显示代替说明文字而已,不向用户报错。因此有了 MainResourceLoader 和 SubresourceLoader 之分。它们的公共基类ResourceLoader 则完成一些两种资源下载都需要完成的操作,比如通过回调将加载进程告知上层应用。
ResourceLoader 通过 ResourceNotifier 类将回调传导到 FrameLoaderClient 类。
主资源的加载是立刻发起的,而派生资源则可能会为了优化网络,在队列中等待 (这里的立刻发起是 loader 层面的,不是 Network 层面的 ) 。 ResourceScheduler 这个类就是用来管理资源加载的调度。主要调度对象就是派生资源,会根据 host 来影响资源加载的先后顺序。
主资源和派生资源的加载还有一个区别,主资源目前是没有缓存的,而派生资源是有缓存机制的。这里的缓存指的是 Resouce Cache ,用于保存原始数据(比如 CSS , JS等),以及解码过的图片数据,通过 Resource Cache 可以节省网络请求和图片解码的时候。不同于 Page Cache , Page Cache 存的是 DOM 树和 Render 树的数据结构,用来在前进后退的时候快速显示页面。
二、加载流程
下图是加载 html 页面时,一个正常的加载流程。
三、主资源加载过程
1. DocumentLoader 调用 MainResourceLoader::load 向 loader 发起请求
2. 调用 MainResourceLoader::loadNow
3. 调用 MainResourceLoader::willSendRequest
4. 调用 ResourceLoader::willSendRequest, 将 callback 通过 ResourceNotifier 传导给FrameLoaderClient 。 Client 可以在回调中操作 ResourceRequest ,比如设置请求头部。
5. 调用 PolicyChecker::checkNavigationPolicy 过滤掉重复请求等
6. loader 调用 ResourceHandle::create 向 Network 发起加载请求
7. 收到第一个 HTTP 响应数据包 ,Network 回调MainResourceLoader::didReceiveResponse ,主要处理 HTTP 头部。
8. 调用 PolicyChecker:: checkContentPolicy, 并最终通过 FrameLoaderClient 的dispatchDecidePolicyForMIMEType 判断是否为下载请求(存在 "Content-Disposition"http 头部)
9. 调用 MainResourceLoader::continueAfterContentPolicy ,根据 ResourceResponse检测是否发生错误。
10. 调用 ResourceLoader::didReceiveResponse ,将 callback 通过 ResourceNotifier 传导给 FrameLoaderClient 。
11. 收到 HTTP 体部数据,调用 MainResourceLoader::didReceiveData
12. 调用 ResourceLoader::didReceiveData ,将 callback 通过 ResourceNotifier 传导给FrameLoaderClient
13. 调用 MainResourceLoader::addData
14. 调用 DocumentLoader::receivedData
15. 调用 DocumentLoader::commitLoad
16. 调用 FrameLoader::commitProvisionalLoad , FrameLoader 从 provisional 状态跃迁到 Committed 状态
17. 调用 FrameLoaderClientQt::committedLoad
18. 调用 DocumentLoader::commitData ,启动 Writer 对象来处理数据(DocumentWriter::setEncoding , DocumentWriter::addData )
19. 调用 DocumentWriter::addData
20. 调用 DocumentParser::appendByte
21. 调用 DecodedDataDocumentParser::appendBytes 对文本编码进行解码
22. 调用 HTMLDocumentParser::append ,进行 HTML 解析
23. 数据来齐,调用 MainResourceLoader::didFinishLoading
24. 调用 FrameLoader::finishedLoading
25. 调用 DocumentLoader::finishedLoading
26. 调用 FrameLoader::finishedLoadingDocument ,启动 writer 对象接收剩余数据,重复 19-22 进行解析
27. 调用 DocumentWriter::end 结束接收数据(调用 Document::finishParsing )
28. 调用 HTMLDocumentParser::finish
四、派生资源加载流程
在派生资源的加载中, SubresourceLoader 更多起到的是一个转发的作用,通过它的client ( SubresourceLoaderClient 类)来完成操作。
各个加载阶段的处理在 SubresourceLoaderClient 的派生类CachedResourceRequest,Loader,IconLoader 中完成。 Client 会创建 SubresourceLoader 。
请求发起阶段, ResourceLoadScheduler 负责对 SubresourceLoader 进行调度。
Document 类会创建 CachedResourceLoader 类的对象 m_cachedResourceLoader, 这个类 ( 对象 ) 提供了对 Document 的派生资源的访问接口 requestImage ,requestCSSStyleSheet , requestUserCSSStyleSheet , requestScript , requestFont ,requestXSLStyleSheet , requestLinkPrefetch 。为了实现这些接口, CachedResourceLoader需要创建 CachedResourceRequest 对象来发起请求。
一般情况下,一个 Document 拥有一个 CachedResourceLoader 类实例。
MemoryCache 类则对提供缓存条目的管理,可以方便地进行 add , remove ,缓存淘汰等。具体的缓存条目则是通过 CachedResource 类存储, MemoryCache 类维护了一个 HashMap 存储所有缓存条目。
HashMap <String,CachedResource> m_resources;
CachedResourceRequest 依赖于 CachedResource, 在 CacheResourceRequest 的构造函数中,会传入 CachedResource 对象作为参数。 CachedResource 既存储响应体部,也存储同 cache 相关的头部。在发起请求前,会检查是否有 cache 的 validator ,在收到响应的时候,则需要更新对应的头部。 CachedResource 类实现了 RFC2616 中的缓存一节。实际上 CachedResource 类真正完成了同网络的通信。 CachedResource 类根据申请的资源类型派生出不同的子类。
CachedResource 类的使用者必须是 CachedResourceClient, 在这个类中维护了CachedResourceClient 类的集合 m_clients 。每一个 Client 通过 addClient 和 removeClient将自己加入到该类的 Client 集合中。 CachedResourceClientWalker 则提供了CachedResouceClient 的一个遍历接口。当数据来齐的时候, CachedResource 类会通过CachedResouceClient::notifyFinished 接口通知使用者。
下图是 Image 元素对应的几个类关系。
下面以 image 为例分析其加载过程
1. 解析 html 页面的时候,解析到 img 标签,调用 HTMLImageElement::create 创建 HTMLImageElement 对象,该对象包含 HTMLImageLoader 对象m_imageLoader
2. 解析到 img 的 href 属性,调用ImageLoader::updateFromElementIgnoringPreviousError
3. 调用 ImageLoader::updateFromElement
4. 调用 CachedResourceLoader::requestImage
5. 调用 CachedResourceLoader::requestResource( 根据缓存的情况确定是否可以从缓存获取,或者需要 revalidate ,或者需要直接从网络获取 )
6. 调用 CachedResourceLoader::loadResource
7. 根据 Resource 的类型调用 createResource 创建对应的 CachedResource
8. 调用 MemoryCache::add 在 cache 中查找是否有对应的 cache 条目,如果没有创建之
9. 调用 CachedImage::load
10. 调用 CachedResource::load
11. 调用 CachedResourceLoader::load
12. 调用 CachedResourceRequest::load
13. 创建 CachedResourceRequest 对象,它将作为 SubresourceLoader 的 client
14. 调用 ResourceLoaderScheduler::scheduleSubresourceLoad
15. 调用 SubresourceLoader::create
16. ResourceLoadScheduler::requestTimerFired
17. 调用 ResourceLoader::start
18. 调用 ResourceHandle::create 发起请求
19. 收到 HTTP 响应头部,调用 ResourceLoader::didReceiveResponse
20. 调用 SubresourceLoader::didiReceiveResponse
21. 调用 CachedResourceRequest::didReceiveResponse 处理响应头部,特别是同缓存相关的头部,比如 304 的 status code
22. 调用 ResourceLoader::didReceiveResponse
23. 收到体部数据,调用 ResourceLoader::didReceiveData
24. 调用 SubresourceLoader::didReceiveData
25. 调用 ResourceLoader::didReceiveData
26. 调用 ResourceLoader::addData 将数据存储到 SharedBuffer 里面
27. 调用 CachedResourceRequest::didReceiveData
28. 数据来齐 , 调用 ResourceLoader::didFinishLoading
29. 调用 SubresourceLoader::didFinishLoading
30. 调用 CachedResourceRequest::didFinishLoading
31. 调用 CachedResource::finish
32. 调用 CachedResourceLoader::loadDone
33. 调用 CachedIm
WebKit内核源代码分析(五)
1. HTML解析模型-HTML解析模型图
图1 HTML解析模型图
上图是HTML解析模型图,HTML解析分成Tokeniser和Tree Construction两个步骤,在”WebKit中的html词法分析”
(http://blog.csdn.net/dlmu2001/archive/2010/11/09/5998130.aspx)一文中,我们已经对Tokeniser这一步进行了分析,本文的目标是Tree Construction这一步。
Tree Construction输入是token流,输出是DOM节点树。
2. DOM树
HTML DOM定义了一套标准来将html文档结构化,它定义了表示和修改文档所需的对象、这些对象的行为和属性以及对象之间的关系,可以把它理解为页面上数据和结构的一个树形表示。
Node是DOM模型中的基础类,它可以分成13类(见NodeType),在HTML解析中,最常见的是Document,Element,Text三类。
l Document是文档树的根节点,在HTML文档中,他派生为HTMLDocument。
l 在文档中,所有的标签转化为Element类,一般它有标签名,并根据标签名继承为特定的子类。
l Element之间的原始文本转化成Text类。
以一个简单的html页面为例:
<html>
<head>
<title>test</title>
</head>
<body>
<h1>hl1</h1>
<h2>hl2</h2>
<h3>hl3</h3>
</body>
</html>
经过解析后的节点树如下(忽略换行符):
图2 HTML DOM节点树示例
如果没有忽略换行符,则每个换行符就是一个Value为”\n”的Text节点。
3. Tree Construction原理
将图二中的节点树以WebKit中的类具体化(同样忽略换行符)。
图3 Webkit HTML DOM节点树示例
看到这里,你是不是觉得仿佛看到了一个呼之欲出的Tree Construction轮廓?是的,最简化的情况就是这样,根据输入的token,创建出相应的Element派生类,然后添加到DOM树中合适的位置,这就是Tree Construction干的事情。当然,添加到合适的位置,这个需要一系列复杂的规则,另外,WebKit将Render树的创建也放到了Tree Construction阶段中来,再加上CSS,Javascript,所以,这就是你看到的复杂的代码。
放出两个函数原型,热热身,培养培养感情。
PassRefPtr<Element> HTMLConstructionSite::createHTMLElement(AtomicHTMLToken& token);
void HTMLConstructionSite::insertHTMLElement(AtomicHTMLToken& token);
Tree Construction流程由一个状态“Insertion Mode”进行控制,它影响token的处理以及是否支持CDATA部分,HTML5中给出了详细的规则(http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#the-insertion-mode)。它也控制了在特定状态下能够处理的token,比如在head里面,再出现head标签,显然是不应该处理的。
4. 开放元素堆栈
为了维护即将解析的标签同已解析的标签之间的关系(此时即将解析的标签还没有加入到DOM树中),引入了开放元素堆栈m_openElements,初始状态下,这个堆栈是空的,它是向下增长的,所以最上面的节点是最早加入到堆栈中的,在html文档中,最上面的节点就是html元素,最底部的节点就是最新加入到堆栈中的。Tree Builder的时候,每碰到一个StartTag的token,就会往m_opnElements中压栈,碰到EndTag的token,则出栈。像Character这样的token,则不需要进行压栈出栈的动作,只有可以包含子节点的tag,才做压栈出栈的动作。Html5的文档中对开放元素堆栈也有说明,http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#the-stack-of-open-elements。
对于正在解析的token,除了根节点html,它必然是堆栈底部元素(m_openElements.top())的子节点,所以在形成DOM树的时候,就可以通过ContainerNode::parserAddChild这样的接口加入到DOM节点树中。
除了正常的堆栈和压栈,对于html,head,body元素,栈结构(HTMLElementStack)中有专门的成员m_htmlElement,m_headElement,m_bodyElement记录,主要是用于检错纠错处理。
在本文的html范例中,当解析到<h2>hl2</h2>的hl2这个character的token的时候,它的开放元素堆栈如下,HTMLHeadingElement是堆栈的top,所以它是hl2这个Text节点的parent。
图4 开放元素堆栈示例
此时的DOM节点树如下:
图5 Webkit DOM节点数示例
5. 元素的创建
HTMLElementFactory类提供了元素的创建方法createHTMLElement。传入为对应的标签名,所属的document,所属的form(如果属于form),在parser的时候,最后一个参数为true。
PassRefPtr<HTMLElement> HTMLElementFactory::createHTMLElement(const QualifiedName& qName, Document* document, HTMLFormElement* formElement, bool createdByParser);
在HTMLElementFactory中,通过一个Hash Map将tag name和对应的元素构造函数对应起来(gFunctionMap)。tag一般对应一个派生于HTMLElement的类。如下是HTMLHeadingElement的类层次结构图。
图6 HTMLHeadingElement类层次图
6. 其它
HTMLConstructionSite::attach中的attach一词,地瓜理解主要是attach到DOM节点数上,当然,它同时调用了Element::attach,Element类的attach主要是attach到Render树上,它会创建对应该Element的RendrObject。
除了m_openElements,HTMLConstructionSite同时维护了Format 元素列表m_activeFormattingElements,Formating元素就是那些格式化标签,包括a,b,big,code,em,font,I,fot,I,nobr,s,small,strike,strong,tt,u。为了处理这些Formatting元素的嵌套关系(此时它们可能不是父子关系,而是平级,不加入到m_openElements),HTML5引入了这个列表(http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#list-of-active-formatting-elements)。
使用gdb调试的童子,可以运行Tools/gdb/webkit.py脚本,在print结构体的时候得到易于理解的表示,还可以打印出节点树,具体参考http://trac.webkit.org/wiki/GDB。
HTTP请求头概述 (HttpServletRequest)
HTTP客户程序(例如浏览器),向服务器发送请求的时候必须指明请求类型(一般是GET或者POST)。如有必要,客户程序还可以选择发送其他的请求 头。大多数请求头并不是必需的,但Content-Length除外。对于POST请求来说Content-Length必须出现。
下面是一些最常见的请求头
Accept:浏览器可接受的MIME类型。
Accept-Charset:浏览器可接受的字符集。
Accept-Encoding:浏览器能够进行解码的数据编码方式,比如gzip。Servlet能够向支持gzip的浏览器返回经gzip编码的HTML页面。许多情形下这可以减少5到10倍的下载时间。
Accept-Language:浏览器所希望的语言种类,当服务器能够提供一种以上的语言版本时要用到。
Authorization:授权信息,通常出现在对服务器发送的WWW-Authenticate头的应答中。
Connection:表示是否需要持久连接。如果Servlet看到这里的值为“Keep-Alive”,或者看到请求使用的是HTTP 1.1(HTTP 1.1默认进行持久连接),它就可以利用持久连接的优点,当页面包含多个元素时(例如Applet,图片),显著地减少下载所需要的时间。要实现这一 点,Servlet需要在应答中发送一个Content-Length头,最简单的实现方法是:先把内容写入 ByteArrayOutputStream,然后在正式写出内容之前计算它的大小。
Content-Length:表示请求消息正文的长度。
Cookie:这是最重要的请求头信息之一
From:请求发送者的email地址,由一些特殊的Web客户程序使用,浏览器不会用到它。
Host:初始URL中的主机和端口。
If-Modified-Since:只有当所请求的内容在指定的日期之后又经过修改才返回它,否则返回304“Not Modified”应答。
Pragma:指定“no-cache”值表示服务器必须返回一个刷新后的文档,即使它是代理服务器而且已经有了页面的本地拷贝。
Referer:包含一个URL,用户从该URL代表的页面出发访问当前请求的页面。
User-Agent:浏览器类型,如果Servlet返回的内容与浏览器类型有关则该值非常有用。
UA-Pixels,UA-Color,UA-OS,UA-CPU:由某些版本的IE浏览器所发送的非标准的请求头,表示屏幕大小、颜色深度、操作系统和CPU类型。
有关HTTP头完整、详细的说明,请参见http://www.w3.org/Protocols/的HTTP规范。
HTTP应答头概述(HttpServletResponse)
Web服务器的HTTP应答一般由以下几项构成:一个状态行,一个或多个应答头,一个空行,内容文档。设置HTTP应答头往往和设置状态行中的状态代码结 合起来。例如,有好几个表示“文档位置已经改变”的状态代码都伴随着一个Location头,而401(Unauthorized)状态代码则必须伴随一 个WWW-Authenticate头。
然而,即使在没有设置特殊含义的状态代码时,指定应答头也是很有用的。应答头可以用来完成:设置Cookie,指定修改日期,指示浏览器按照指定的间隔刷新页面,声明文档的长度以便利用持久HTTP连接,……等等许多其他任务。
设置应答头最常用的方法是HttpServletResponse的setHeader,该方法有两个参数,分别表示应答头的名字和值。和设置状态代码相似,设置应答头应该在发送任何文档内容之前进行。
setDateHeader方法和setIntHeadr方法专门用来设置包含日期和整数值的应答头,前者避免了把Java时间转换为GMT时间字符串的麻烦,后者则避免了把整数转换为字符串的麻烦。
HttpServletResponse还提供了许多设置
setContentType:设置Content-Type头。大多数Servlet都要用到这个方法。
setContentLength:设置Content-Length头。对于支持持久HTTP连接的浏览器来说,这个函数是很有用的。
addCookie:设置一个Cookie(Servlet API中没有setCookie方法,因为应答往往包含多个Set-Cookie头)。
另外,如上节介绍,sendRedirect方法设置状态代码302时也会设置Location头。
有关HTTP头详细和完整的说明,请参见http://www.w3.org/Protocols/规范。
HTTP应答头 说明
Allow 服务器支持哪些请求方法(如GET、POST等)。
Content-Encoding 文档的编码(Encode)方法。只有在解码之后才可以得到Content-Type头指定的内容类型。利用gzip压缩文档能够显著地减少HTML文档 的下载时间。Java的GZIPOutputStream可以很方便地进行gzip压缩,但只有Unix上的Netscape和Windows上的IE 4、IE 5才支持它。因此,Servlet应该通过查看Accept-Encoding头(即request.getHeader("Accept- Encoding"))检查浏览器是否支持gzip,为支持gzip的浏览器返回经gzip压缩的HTML页面,为其他浏览器返回普通页面。
Content-Length 表示内容长度。只有当浏览器使用持久HTTP连接时才需要这个数据。如果你想要利用持久连接的优势,可以把输出文档写入 ByteArrayOutputStram,完成后查看其大小,然后把该值放入Content-Length头,最后通过 byteArrayStream.writeTo(response.getOutputStream()发送内容。
Content-Type 表示后面的文档属于什么MIME类型。Servlet默认为text/plain,但通常需要显式地指定为text/html。由于经常要设置 Content-Type,因此HttpServletResponse提供了一个专用的方法setContentTyep。
Date 当前的GMT时间。你可以用setDateHeader来设置这个头以避免转换时间格式的麻烦。
Expires 应该在什么时候认为文档已经过期,从而不再缓存它?
Last-Modified 文档的最后改动时间。客户可以通过If-Modified-Since请求头提供一个日期,该请求将被视为一个条件GET,只有改动时间迟于指定时间的文 档才会返回,否则返回一个304(Not Modified)状态。Last-Modified也可用setDateHeader方法来设置。
Location 表示客户应当到哪里去提取文档。Location通常不是直接设置的,而是通过HttpServletResponse的sendRedirect方法,该方法同时设置状态代码为302。
Refresh 表示浏览器应该在多少时间之后刷新文档,以秒计。除了刷新当前文档之外,你还可以通过setHeader("Refresh", "5; URL=http://host/path")让浏览器读取指定的页面。注意这种功能通常是通过设置HTML页面HEAD区的<META HTTP-EQUIV="Refresh" CONTENT="5;URL=http://host/path">实现,这是因为,自动刷新或重定向对于那些不能使用CGI或Servlet的 HTML编写者十分重要。但是,对于Servlet来说,直接设置Refresh头更加方便。注意Refresh的意义是“N秒之后刷新本页面或访问指定 页面”,而不是“每隔N秒刷新本页面或访问指定页面”。因此,连续刷新要求每次都发送一个Refresh头,而发送204状态代码则可以阻止浏览器继续刷 新,不管是使用Refresh头还是<META HTTP-EQUIV="Refresh" ...>。注意Refresh头不属于HTTP 1.1正式规范的一部分,而是一个扩展,但Netscape和IE都支持它。
Server 服务器名字。Servlet一般不设置这个值,而是由Web服务器自己设置。
Set-Cookie 设置和页面关联的Cookie。Servlet不应使用response.setHeader("Set-Cookie", ...),而是应使用HttpServletResponse提供的专用方法addCookie。参见下文有关Cookie设置的讨论。
WWW-Authenticate 客户应该在Authorization头中提供什么类型的授权信息?在包含401(Unauthorized)状态行的应答中这个头是必需的。例 如,response.setHeader("WWW-Authenticate", "BASIC realm=/"executives/"")。注意Servlet一般不进行这方面的处理,而是让Web服务器的专门机制来控制受密码保护页面的访问 (例如.htaccess)。
HTTP请求解析过程 (简单概括)
1.域名解析
用户输入网址,由域名系统DNS解析输入的网址;
2.TCP的3次握手
通过域名解析出的IP地址来向web服务器发起TCP连接请求,如果3次握手通过,则与web服务端建立了可靠的连接;
3.发送http请求
web客户端向web服务端发送请求;
4.接收http响应
web客户端接收来自web服务端的响应,包含各种根据请求反馈的数据;
5.web客户端(浏览器)解释响应
最后由浏览器解析响应里的数据,即HTML代码,以及HTML代码中请求的资源,然后由浏览器呈现给用户。
以上就是对一个HTTP请求网页的解析过程的简单概括
HTTP——概述、请求和响应、GET和POST请求
HTTP协议概述
WEB浏览器与WEB服务器之间的一问一答的交互过程必须遵循一定的规则,这个规则就是HTTP协
HTTP是hypertext transfer protocol(超文本传输协议)的简写,它是TCP/IP协议之上的一个应用层协议,用于定义WEB浏览器与WEB服务器之间交换数据的过程以及数据本身的格式。
1.HTTP协议到底约束了什么:
1.约束了浏览器以何种格式向服务端发生数据:
2.约束了服务器应该以何种格式来接受客户端发送的数据:
3.约束了服务器应该以何种格式来反馈数据给浏览器;
4约束了浏览器应该以何种格式来接收服务器反馈的数据.
2.HTTP格式规范
1.HTTP1.0规范:
若请求的有N个资源,得建立N次连接,发送N次请求,接收N次响应,关闭N次连接.
每次请求的之间都要建立单独的连接,请求,响应,响应完关闭该次连接:
缺点:每请求一个资源都要单独的建立新的连接,请求完并关闭连接.
1.HTTP1.1规范:
能在一次连接之间,多次请求,多次响应,响应完之后再关闭连接.
在一个TCP连接上可以传送多个HTTP请求和响应
多个请求和响应过程可以重叠进行
增加了更多的请求头和响应头
3.HTTP协议的版本
HTTP/1.0、HTTP/1.1、HTTPS2.0
请求信息
1.一个完整的由客户端发给服务器的HTTP请求中包含以下内容:
*请求行
包含了请求方法、请求资源路径、HTTP协议版本
Get/resources/imges/1.jpg/ HTTP/1.1
*多个请求头
包含了对客户端的环境描述、客户端请求的主机地址等信息
1.Accept:浏览器可接受的MIME类型(Tomcat安装目录/conf/web.xml中查找)注意: MIME: 表示文件内容的类型.
2.Accept-Charset:告知服务器,客户端支持哪种字符集
3.Accept-Encoding:浏览器能够进行解码的数据编码方式
4.Accept-Language:浏览器支持的语言。
5.Referer:当前页面由哪个页面访问过来的。
6.Content-Type:通知服务器,请求正文的MIME类型。
7.Content-Length:请求正文的长度
8.If-Modified-Since:通知服务器,缓存的文件的最后修改时间。
9.User-Agent:通知服务器,浏览器类型.
10.Connection:表示是否需要持久连接。如果服务器看到这里的值为“Keep -Alive”,或者看到请求使用的是HTTP 1.1(HTTP 1.1默认进行持久连接)
11.Cookie:这是最重要的请求头信息之一(会话有关)
2.请求实体
服务器返回给客户端的具体数据,比如文件数据.
![](https://img2020.cnblogs.com/blog/1668748/202004/1668748-20200425215713826-779651544.png)
常见状态响应码
状态码 中文描述
1xx 服务器未完全接收客户端信息,等待一段时间,发1xx
2xx 成功(200)
3xx 重定向,对于客户端的请求,服务器将请求转发到另外一个服务器来访问(304)
4xx 客户端发生异常。404:(请求路径没有对应的资源),(405)请求没有对应的doxxx()方法
5xx 服务端发生异常
Get个Post方法
简单说明:在HTTP/1.1协议中,定义了8种发送http请求的方法
GET、POST、OPTIONS、HEAD、PUT、DELETE、TRACE、CONNECT、PATCH
1.GET请求
1.请求的数据全部在浏览器的地址栏(很不安全)
ttp://localhost:8080/webapp/?username=coder&email=111#
2.请求信息全部会存储在请求行中
注意:由于浏览器和服务器对URL长度有限制,因此在URL后面附带的参数是有限制的,通常不能超过2KB
3.参数连接
资源?参数名=参数值值&参数名=参数值&…
2.POST请求
请求的数据不会出现在浏览器的地址栏中(比较安全)
请求信息会全部存储到请求实体中
3.GET和POST请求的区别:
1.GET的请求数据在地址栏中,而POST不会(Post比Get安全)
2.POST请求的参数存放于请求实体中, 而GET存放在请求行中.
GET请求的数据不能超过2K, 而POST没有上限 文件上传时,必须使用POST方式
3.GET可以缓存, 而POST没有缓存
4.如何选择GET和POST
1.如果要传递大量数据,比如文件上传,只能用POST请求
2.GET的安全性比POST要差些,如果包含机密\敏感信息,建议用POST
3.如果仅仅是索取数据(数据查询),建议使用GET
4.如果是增加、修改、删除数据,建议使用POST
HTTP无状态连接
1.打开一个浏览器,访问某一个站点,在该网址内部查看信息,点击超链接等相关操作,最后关闭浏览器的整个过程,称之为一次会话
2.HTTP协议
1.它是无状态连接,服务器不知道上一次是哪个客户端请求了自己.
2.无状态连接带来的问题:
*在一次会话中,多个请求之间无法共享数据,无法跟踪用户的会话信息.
*在一次会话*享数据即会话跟踪技术.
3.解决方案
*使用参数的传递机制
在每一个请求之间使用参数来传递需要共享的数据.http://localhost/param/list?username=gzy
可以解决问题,但请求需要共享的数据全部暴露在URL中(请求行),不安全.想要解决这个问题,把共享的数据存放到请求头中. 期待Cookie技术
#-*- coding:utf-8 -*-
@Author: A dog
PS:如有需要Python学习资料的小伙伴可以加点击下方链接自行获取
python免费学习资料以及群交流解答点击即可加入
@File: Taobao.py
@Software: PyCharm
import datetime import time
from selenium import webdriver
name = ‘账号’ password = ‘密码’
指定webdriver位置
driver = webdriver.Chrome(executable_path=‘C:\chromedriver.exe’)
打开淘宝网址
driver.get(‘https://www.taobao.com/’)
class pay: # 登录模块 def login_in(self, num, pwd, times): # 点击登入 driver.find_element_by_class_name(‘h’).click() time.sleep(0.3) # 发送账号密码 driver.find_element_by_id(‘fm-login-id’).send_keys(num) driver.find_element_by_id(‘fm-login-password’).send_keys(pwd) time.sleep(0.2) # 点击登入 driver.find_element_by_class_name(‘fm-btn’).click() time.sleep(1) # 进入购物车 driver.get(“https://cart.taobao.com/cart.htm”) # driver.find_element_by_id(‘mc-menu-hd’).click() # time.sleep(0.2) driver.find_element_by_id(‘J_SelectAll1’).click() time.sleep(0.5) self.auto_check1(times)
#反复结算
def auto_check(self,times):
while True:
try:
if driver.find_element_by_id(‘J_SelectAll1‘):
driver.find_element_by_id(‘J_SelectAll1‘).click()
time.sleep(0.5)
break
except:
time.sleep(0.5)
pass
while True:
if datetime.datetime.now().strftime(‘%Y-%m-%d %H:%M:%S‘) >= times:
while True:
try:
driver.find_element_by_id("J_Go").click()
print("成功结算")
driver.find_element_by_link_text(‘提交订单‘).click()
print(f"抢购成功,请尽快付款")
time.sleep(5)
return 0
except:
print("无法结算,重试")
time.sleep(1)
driver.get("https://cart.taobao.com/cart.htm")
self.auto_check(times)
def auto_check1(self,times):
while True:
if datetime.datetime.now().strftime(‘%Y-%m-%d %H:%M:%S.%f‘) >= times:
while True:
try:
driver.find_element_by_id("J_Go").click()
print("成功结算")
driver.find_element_by_link_text(‘提交订单‘).click()
print(f"抢购成功,请尽快付款")
time.sleep(5)
return 0
except:
print("无法结算,重试")
time.sleep(1)
driver.get("https://cart.taobao.com/cart.htm")
self.auto_check(times)
# 运行
def run_driver(self, num, pwd, times):
self.login_in(num, pwd, times)
qq:
if name == ‘main’: jd = pay() jd.run_driver(name, password, ‘2020-12-15 19:59:59:400’)