Mozilla Firefox Extension扩展 内幕 教程 源代码分析 安装过程分析(XPInstall,xpcom,rdf,xpi,chrome,manifest)

一、分析任务说明

本报告的工作内容是对firefox源代码中跟它的扩展(extensions)部分相关的代码进行研究,总结得到firefox的扩展(extensions)相关部分的架构,并尽量细致的分析extensions安装过程,启动过程和相关功能类之间的关系。

本报告将在第三节详细的介绍firefox扩展的结构和相关的代码关系。

二、firefox扩展的相关知识

2.1 扩展(extensions)

      Extensions添加新功能到Mozilla Firefox中。Extensions可以简单添加一个工具栏按钮,也可以实现一个完整的新功能。Extensions可以让firefox更加适合个人需要。

      Extensions跟plugins(插件)不同。插件帮助浏览器显示特殊内容,例如播放多媒体文件。常见的插件是flash player。而Extensions也跟搜索引擎插件不同,搜索引擎插件只是在搜索栏里边多加入一个搜索引擎地址而已。

      一个扩展,通常是一个XPI(Cross-Platform Installer Module)包,其实是一个zip类型的压缩包,里边包括必须的文件。在下边的图1中,显示了一个标准的扩展包括的文件和文件的目录结构。

图 1 firefox扩展目录结构

      在图 1中,content 目录下面存放的是扩展的描述界面的 XUL 文件和增加行为的 JavaScript 文件。locale 目录存放的是本地化相关的文件。如果需要支持英文和中文,就可以在 locale 目录下面新建 en-US 和 zh-CN 目录来存放相应的本地化字符串。skin 目录存放的是一些 CSS 文件,用来定义扩展的外观。chrome.manifest 是 Chrome 注册的清单文件(参见 2.2节)。install.rdf 分别包含了扩展安装的信息。

2.2 chrome

Chrome 指的是应用程序窗口的内容区域之外的用户界面元素的集合,这些用户界面元素包括工具条,菜单,进度条和窗口的标题栏等。Chrome 提供者能为特定的窗口类型(如浏览器窗口)提供 chrome。有三种基本的 chrome 提供者:

l  内容(Content):通常是 XUL(XML User Interface Language) 文件。 而XUL文件将会指定扩展在Firefox中运行时表现的界面和功能。XUL文件是一种Javascript文件,设计的目的是为了描述窗口和对话框的内容。

l  区域(Locale) :存放本地化信息。

l  皮肤(Skin):描述 chrome 的外观。通常包含 CSS 和图像文件。

2.3 XULRunner

      XULRunner 项目提供一套称为XULRunner的Mozilla运行支持包,用于启动基于XUL+XPCOM(见2.4节)的程序,例如Firefox,Thunderbird,Sunbird。它提供各种机制,包括安装,升级,卸除这些软件的功能。

      而Firefox整个程序主界面和扩展的界面都是由XUL文件来描述的,所以在Firefox运行过程中,和扩展使用过程中,都是靠XULRunner来支持。

2.4 XPCOM

XPCOM(Cross Platform Component Object Model)是一种跨平台组件对象模型,类似于微软的 COM。它有多种语言系结(Language Binding),使 XPCOM 组件可使用并实现于C++、JavaScript、Java 及 Python。XPCOM 的界面是由称为 XPIDL (Interface Definition Language)所定义。

XPCOM 自身提供了一套核心组件和类别,例如,档案和内存管理、线程、基本数据结构(strings, arrays, variants)等。大多数 XPCOM 组件并非由核心组件所提供,而是由其他平台或应用程序、甚至是延伸套件所提供。

      而在Firefox中,绝大多数的功能都基于XPCOM机制。例如Firefox为扩展开发提供的接口都是用XPCOM方式来实现的。

      例如gRDF = Components.classes["@mozilla.org/rdf/rdf-service;1"]

                                .getService(Components.interfaces.nsIRDFService);

将获取RDF模块的nsIRDFService实例。

2.5 XPInstall

      XPInstall(Cross-Platform Install)是Mozilla系列软件或者其他基于XUL的软件中用于安装extensions的技术。在Firefox2.0的源代码中,有一个名为xpinstall的文件夹,存放跟XPInstall模块相关的代码。

      另外需要特别指出Firefox的扩展分为普通默认扩展和定制安装扩展。普通默认扩展没有定制的安装代码,整个安装过程由Firefox默认流程来执行。而定制安装扩展的安装包中有定制的安装过程信息。而这些信息是用专门的Javascript代码编写,调用XPInstall提供的API来实现。定制安装扩展,常见的例子是用Firefox打开扩展中心,点击某个扩展就可以在线安装,其中就是调用了XPInstall的API。

三、源代码分析结果

3.1 Firefox扩展结构图

图 2 Firefox扩展结构图

图 2所示的是Firefox中跟扩展相关的部分的结构。其中Necko是Firefox处理网络相关的核心部分,而Gecko是Firefox解释网页的核心,或者说内核,也就是Layout Engine。而RDF模块实现了一系列XPCOM接口,用于在安装扩展或者处理扩展过程中,读取RDF文件信息。

扩展安装过程,主要有XPInstall负责,如果XPInstall发现扩展是普通默认扩展,将交由toolkit模块(图中没有画出)中负责扩展安装的相关部分来处理。

扩展的运行,需要Chrome注册模块支持。在Firefox运行时,扩展的chrome信息注册到注册模块中,再交给XULRunner来显示,其中涉及到所有内核提供的功能都涉及到XPCOM。而Firefox所有界面都是由XULRunner来实现的。所以,在扩展运行过程中,扩展已经和Firefox本身融为一体,扩展跟Firefox内核的交互,和Firefox主界面跟内核的交互,两种交互是一样的,都是在XULRunner的平台上,调用XPCOM来实现相应的功能。

3.2 扩展的安装过程

      扩展概要的安装过程如图 3所示:

Firefox截获安装事件

检测rdf安装信息

抽取安装代码

执行安装代码

图 3 扩展的概要安装过程

XPInstall检测扩展

解压xpi保存到相应文件夹(新建或者替换原有文件)

完成安装

普通默认扩展

定制安装扩展

合适安装

不合适安装

      扩展安装主要由XPInstall负责,而XPInstall模块中处理扩展安装的最顶端入口是nsSoftwareUpdateRun.cpp。该文件中定义了RunInstall()函数,Firefox主程序可以设置参数使得RunInstall()单线程或者多线程调用RunInstallOnThread()进行安装。

图 4 XPInstall初始安装过程

图 4是XPInstall安装扩展开始的几步关键过程。其中,nsSoftwareUpdate类包含若干跟安装有关的操作,而nsIZipReader是一个XPCOM接口,有读取xpi包的接口,而installInfo类封装了当前安装的扩展的关键信息,而最后的nsExtensionManager也是XPCOM接口,由toolkit模块中负责Extension部分的代码实现,专门处理普通默认扩展。

上述过程中,有两步比较关键,OpenAndValidateArchive()验证当前的xpi符合规范,数字签名安全等等。另外Test(“install.rdf”),决定了当前这个xpi是否属于普通类型。如果install.rdf存在,则把当前扩展当作普通扩展来处理。再调用nsExtensionManager来进行下一步的安装。

接下来将重点总结一下nsExtensionManager中的安装过程。如图 5所示:

图 5 nsExtensionManager安装过程

在nsExtensionManager中,用Components.classes["@mozilla.org/rdf/rdf-service;1"].getService(Components.interfaces.nsIRDFService),获取nsIRDFService服务。调用getInstallManifest()在install.rdf中获取安装信息ds存到全局变量_metadataDS中。

再调用nsIRDFService的GetResource( )抽取每项安装信息,例如maxVersion,扩展的唯一标识guid等,并检查是否适合安装。

通过getInstallLocation获取安装位置后调用Install类的函数installFromFile()来完成安装。其中即调用_installExtensionFiles()和safeInstallOperation()来部署目标文件夹(window xp为例,是C:\Documents and Settings\UserName\Application Data\Mozilla\Firefox\Profiles\Profile\extensions),然后解压所有xpi中的文件放到指定位置。其中safeInstallOperation()保证了复制文件的过程中,如果出现错误能够完整的rollback。

nsExtensionManager最后调用_updateExtensionManifest()更新扩展信息表,把新扩展的文件信息加到汇总表中,以供Firefox启动的时候由toolkit来读取。详细见3.5节。

如果没有install.rdf,XPInstall将会调用图 6的过程。其中最重要的是InitXPInstallObjects( )。这个调用将会在nsJSInstall中调用到几乎整个XPInstall的类。由于本次分析主要集中在普通默认扩展上,对XPInstall具体过程不作深究。XPInstall的结构请见3.3节。

图 6 定制安装的扩展安装过程

3.3 XPInstall模块分析

表格 1 XPInstall主要类

类名

功能描述

nsInstall

实现大多数xpinstall对外的API(详细见xpinstall的API说明,本报告中略), 在类中记录当前安装包的信息

nsInstallUninstall

记录某个待删除的扩展

nsISoftwareUpdate

Xpinstall中更新扩展或者插件用到的公共接口,例如InstallChrome( )

nsSoftwareUpdate

实现nsISoftwareUpdate

nsXPInstallManager

管理待安装扩展的列表,并提供外围功能,例如下载扩展,完成安装等

nsInstallFolder

安装过程中使用到的目录对象

nsInstallFile

安装过程中使用到的文件对象

nsInstallInfo

记录当前安装的扩展的详细信息,并有重要的函数 GetExtensionManager()。该函数返回nsIExtensionManager接口的实体,专门用于默认extension(如上边章节描述)的安装。

nsXPITrigger

控制扩展安装开始 例如AllowInstall(),InstallChrome()等函数

nInstallVersion

扩展的版本,实现前后对比等函数

nsInstallObject

很基本的基类

nsWinProfile

负责读写extensions.ini等文件

nsInstallResources

读取资源时辅助性的功能

      XPInstall主要类图如图 7所示。

图 7 XPInstall主要类结构图

3.4 rdf模块分析

表格 2 RDF模块主要类和接口

类名

功能描述

对应接口

RDFServiceImpl

整个rdf模块的核心。实现大部分对外的接口。特别是GetDataSource()

nsIRDFService

RDFContentSinkImpl

存储rdf内容的类,并对内容有相应的操作和一些辅助性函数 例如GetResourceAttribute()

nsIRDFContentSink

RDFXMLDataSourceImpl

负责操作xml类型的rdf文件

nsIRDFXMLSource nsIRDFDataSource

nsRDFXMLParser

一个辅助类,用来解释xml文件

nsIRDFXMLParser

RDFContainerUtilsImpl

建立RDFContainer的类

nsIRDFContainerUtils

RDFContainerImpl

管理数据源

nsIRDFContainer

rdfutil

简单的对字符串操作的辅助类

nsRDFXMLSerializer

用于把rdf串行化

nsIRDFXMLSerializer

RDF模块主要类与接口的关系如图 8所示。

图 8 RDF模块主要类与接口的关系图

3.5 扩展的启动过程

Toolkit获取extensions.ini内容

nsChromeRegistry处理每个扩展的manifest文件

nsXULDocument获取有效扩展的XUL信息

XULRunner启动

图 9 扩展启动过程

      扩展的启动过程如图 9所示:

  1. toolkit的nsXREDirProvider读取extensions.ini,抽取每个扩展的chrome.manifest文件。
  2. nsChromeRegistry实现nsIXULOverlayProvider和nsIToolkitChromeRegistry接口。其中ProcessManifestBuffer()处理所有manifest文件,然后把相应的内容加到对应的容器中,例如overlay 的xul对象加到mOverlayHash中。这些容器都是静态的。
  3. XUL组件的nsXULDocument调用nsIXULOverlayProvider的GetXULOverlays()获取扩展的xul列表,并把扩展的xul加到主模型中。
  4. XULRunner根据XUL文件启动Firefox。

四、总结与体会

4.1 分析的难点

  1. 在分析Firefox源代码的过程中,遇到最大的问题就是扩展涉及到的面太广,虽然支持文档不少,但是想直接分析相对应的代码就没有现成的支持了。从最早计划研究Firefox对扩展的支持API,逐渐深入了解扩展的机制后,不得不改变方向。因为扩展涉及到的源代码实在太多,而且Firefox除了内核以外,其他部分都是基于XUL和XPCOM的。正因为这样的架构,使得扩展非常的简单,但我们研究的难度就很大。例如XUL,本来就是Firefox的一个子项目,要独立分析这一部分已经是一个很大的工作。
  2. Firefox源代码非常庞大,查找出哪些代码跟扩展相关也已经是一个很大的工作。
  3. 分析代码的工具不足。例如Firefox扩展涉及到三种类型的代码:C++代码,IDL代码和Javascript代码。C++代码还有比较好的编辑器来阅读,但还是不够,因为要研究函数的调用顺序的时候就非常的困难,因为源程序没有编译,没有代码导航功能。而IDL和Javascript就更加难以阅读了。在研究过程中,不得不使用ctrl+f来不断查找了。
  4. 对组内大部分同学来说,都是第一次正式的去研究一个软件某一部分的代码。经常这一次的过程,让我们学会了如何在软件的源代码中找出我们需要的部分。
  5. 当然,这次过程最大的收获是为我们开发扩展垫好基础,让我们从外到内很好的理解了扩展的结构和扩展的工作机制。这样在进行开发的时候,对XUL和rdf相关编写规范就会更加的容易理解和上手。而这一点,也是我们研究扩展相关代码的最大目的。

4.2 分析的收获

五、工作展望

      在这一次的研究过程中,由于时间和能力有限,研究的广度和深度还是很皮毛。在日后,我们将会围绕着扩展来逐步深入对Firefox源代码的研究。例如首先的研究方向是XPCOM的具体结构和XULRunner这部分。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏腾讯数据库技术

只读实例与RO组--助力MySQL实现读写分离,提升扩展性

3304
来自专栏喵了个咪的博客空间

PhalGo-Echo路由

PhalGo-Echo路由 ? Echo官网地址:https://labstack.com/echo Echo是PhalGo最核心的组件,负责了整体的请求路由返...

3078
来自专栏码洞

迟来的HTTP2简明教程

这是一段来自维基百科的关于HTTP2的说明,截止2015年底,主流浏览器都已经对HTTP2做了支持,根据2017年11月的W3Techs报告说明,全球有1/5的...

531
来自专栏闵开慧

hadoop集群调优分两个方面,map和reduce

hadoop集群调优分两个方面,map和reduce map调优:         map 任务执行会产生中间数据,但这些中间结果并没有直接IO到磁盘上,而...

3265
来自专栏Crossin的编程教室

【Python 第31课】 读文件

之前,我们写的程序绝大多数都依赖于从命令行输入。假如某个程序需要输入很多数据,比如一次考试的全班学生成绩,再这么输就略显痛苦了。一个常见的办法就是把学生的成绩都...

3337
来自专栏Crossin的编程教室

【Python 第1课】安装

在Windows系统上安装Python的方法还算简单,比平常装个软件稍稍麻烦一点。进入Python的官方下载页面Python.org/download,你会看到...

2527
来自专栏cloudskyme

关于操作权限

第1章 引言 1.1 编写目的 详细说说操作权限并且在sshpermissions中是如何处理及使用操作权限的。 1.2 关于操作 这里所说的操作权限是指在我们...

3394
来自专栏从零开始学自动化测试

pytest文档3-pycharm运行pytest

上一篇pytest文档2-用例运行规则已经介绍了如何在cmd执行pytest用例,平常我们写代码在pycharm比较多 写完用例之后,需要调试看看,是不是能正常...

963
来自专栏漏斗社区

安全运维中基线检查的自动化之ansible工具巧用

前几周斗哥分享了基线检查获取数据的脚本,但是在面对上百台的服务器,每台服务器上都跑一遍脚本那工作量可想而知,而且都是重复性的操作,于是斗哥思考能不能找到一种方法...

823
来自专栏IMWeb前端团队

解放双手:如何在本地调试远程服务器上的Node代码

写在前面 谈到node断点调试,目前主要有三种方式,通过node内置调试工具、通过IDE(如vscode)、通过node-inspector,三者本质上差不多。...

3588

扫码关注云+社区