XPCOM指南-7_结束编写WebLock

来源:互联网 发布:淘宝号怎么升心 编辑:程序博客网 时间:2024/06/04 19:32

现在您已创建组件的大多数基础结构。 该组件可以被XPCOM和类别管理器识别,那样在 XPCOM初始化时,它就可以开启动。 组件启动时,它会读取存储在本地文件系统里Url列表,填充到相应的二进制结构。

Using Frozen Interfaces

然而,现在阻止站点的核心功能仍然缺失。 这个接口仍然没有冻结,组件开发人员需要讨论那些功能应该完全公开,所以API没有准备好发布。 这让你处于和大多数使用Mozilla的开发者一样的境地—你想使用一些特定的功能,但是接口似乎天天都在变。

 

所有的Mozilla的源代码是公开的,并且接口使用也很容易。 抓一个合适的头文件,使用组件或者服务管理器访问你需要的接口和XPCOM对象,实现你将会在你的项目里实现的接口。 这带来了很大的灵活性,当然你会失去一些兼容性。 如果你使用没有冻结的“东西”,那个东西将来可能随着Gecko的版本变化而变化。

 

如果你想你的组件能够对来自Gecko的变化免疫,你必须只使用已经冻结的接口和APIs。 一般这会在接口申明上面的进行说明。 例如,下面的nsIServiceManager

 

/**

 * The nsIServiceManager manager interface provides a means to obtain

 * global services in an application. The service manager depends

 * on the repository to find and instantiate factories to obtain

 * services.

 *

 * Users of the service manager must first obtain a pointer to the

 * global service manager by calling NS_GetServiceManager. After that,

 * they can request specific services by calling GetService.

 * When they are finished they can NS_RELEASE() the service as usual.

 *

 * A user of a service may keep references to particular services

 * indefinitely and only must call Release when it shuts down.

 *

 * @status FROZEN

 */

 

冻结的接口和函数时GECKO SDK的一部分。 有一个经验,SDK外部的接口可当作是“实验性”的或者未冻结的。 参见侧边拦的信息,它描述了冻结或未冻结的接口怎么影响你的软件开发,它还包括了接口变化会导致严重破坏的一些技术细节。

 

使用未冻结接口的危险性

 

假设你在您的组件里使用了未冻结的接口,nsIFoo,这个接口在你测试时使用的那个版本的Gecko很正常。 然而在将来某个时候,nsIFoo有了重大变化,它的方法重新排序,添加,删除了一些。 此外,这个接口不应该被其它的GeckoMozilla客户使用,接口的维护人员也不知道有其它的开发者使用了这个接口,他并不会改变接口的IID。 当你的组件在此新版本的Gecko里运行时,你的方法调用将会通过不是你期望的虚表(v-table)路由,这将会产生一个不可预期的结果,甚至导致崩溃。

 

看下面的图,组件编译时使用的nsIFoo接口有三个方法。 组件调用TestA方法,传入一个整数10。 这个可以在任何如编译时使用的相同版本的Gecko的应用上正常运行。 然而当nsIFoo接口发生了变化,TestA方法被删除了;虚表里的第一个函数现在成为了IsPrime()。 当组件调用TestA时,实际上它是调用的方法IsPrime。 不用说,这很不爽。 此外,还没有一种方式,容易的方式来发现这种类型的运行时错误。

 

 

Gecko开发人员可以更改该接口的IID,和一些其它工作。这可以防止很多类似错误。但未冻结的接口不提供任何正式方式支持,和为接口的任何变化提供不同的IID接口并不是一个好主意。

当使用冻结的接口,你就不用担心以后Gecko版本变化会带来兼容性问题。 唯一的问题是,编译器的变化,导致虚表的布局的变化,这种变化只在编译器的ABI变化是会发生。 例如,2002年的GCC 3.2改变了C++ABI,这将会影响所有用GCC3.2编译的应用程序。 相应的问题在GCC 4.0时也发生过。

 

在你使用未冻结的接口之前,你应该联系负责你使用的代码的开发者,问问他。 他们可能会建议你是否使用这个接口,或者建议你使用替代的方法,或者在代码更改时通知你。

 

本项目,我们需要一个叫做nsIContentPolicy的接口。 在写本书的时候,这个接口正在接受审查。 接口到达此状态时,意味着,模块的主人和同行正在讨论如何把此接口更好的公开。 通常这种标记的接口只会做细微的修改。 甚至这个接口被标记为“审计中”,然而,你打算使用此接口时,与负责人联系联系仍然是个好主意。

 

Copying Interfaces into Your Build Environment

在你的组件里获取和实现得接口不是Gecko的一部分,简单的,在Gecko SDK创建一个叫做 unfrozen的新路径。 从Gecko的源代码路径,content/base/public 复制头文件和IDL文件到你新建的目录(对于WebLock组件,你所有需要的是nsIContentPolicy 头文件和nsIContentPolicy.idl)。  然后,与你创建Weblock.h的步骤相似,使用xpidl编译器从IDL文件创建头文件。 一旦你有了这个接口的头文件,你可以修改WebLock类实现nsIContentPolicy 接口。 现在Weblock类支持4个接口:nsISupports, nsIObserver, nsIContentPolicy, 和 iWeblock.

WebLock Interfaces

Interface Name

Defined by

Status

Summary

nsISupports

XPCOM

Frozen

Provides interface discovery, and object reference counting

nsIObserver

XPCOM

Frozen

Allows messaging passing between objects

nsIContentPolicy

Content

Not Frozen

Interface for policy control mechanism

iWeblock

Web Lock

Not Frozen

Enables and disables Weblock. Also, provides access to the URL that are whitelisted.

Implementing the nsIContentPolicy Interface

实现新的接口,你必须 #include未冻结的nsIContentPolicy,你还必须保证编译系统可以找到它。 把它添加到你的编译系统吧。

 

一旦你确认你的组件可以使用新的头文件进行编译,你必须从nsIContentPolicy接口派生你的类,定义类的时候,你可以在类公共申明里加入宏NS_DECL_NSICONTENTPOLICY,它提供了nsIContentPolicy的所有方法定义。 更新后的WebLock类如下所示:

 

class WebLock: public nsIObserver, public iWeblock, public nsIContentPolicy

{

  public:

    WebLock();

    virtual ~WebLock();

 

    NS_DECL_ISUPPORTS

    NS_DECL_NSIOBSERVER

    NS_DECL_IWEBLOCK

    NS_DECL_NSICONTENTPOLICY

 

  private:

    urlNode* mRootURLNode;

    PRBool   mLocked;

};

 

记住改变nsISupports实现宏,包含进nsIContentPolicy ,这样Gecko的其它部分才知道WebLock支持nsIContentPolicy 接口。

 

NS_IMPL_ISUPPORTS3(WebLock, nsIObserver, iWeblock, nsIContentPolicy);

 

Receiving Notifications

为了能接收通知,你必须注册一个新的分类。 你已经注册了一个接收启动通知的分类。 现在你要使用的分类名字叫做“content-policy”。 把WebLock组件添加到这个分类,修改WebLockRegistration 回调函数,如下所示:

 

static NS_METHOD WebLockRegistration(nsIComponentManager *aCompMgr,

                                     nsIFile *aPath,

                                     const char *registryLocation,

                                     const char *componentType,

                                     const nsModuleComponentInfo *info)

{

  nsresult rv;

  nsCOMPtr<nsIServiceManager> servman = do_QueryInterface((nsISupports*)aCompMgr, &rv);

  if (NS_FAILED(rv))

    return rv;

 

  nsCOMPtr<nsICategoryManager> catman;

  servman->GetServiceByContractID(NS_CATEGORYMANAGER_CONTRACTID,

                                  NS_GET_IID(nsICategoryManager),

                                  getter_AddRefs(catman));

  if (NS_FAILED(rv))

    return rv;

 

  char* previous = nsnull;

  rv = catman->AddCategoryEntry("xpcom-startup",

                                "WebLock",

                                WebLock_ContractID,

                                PR_TRUE,

                                PR_TRUE,

                                &previous);

  if (previous)

    nsMemory::Free(previous);

 

  rv = catman->AddCategoryEntry("content-policy",

                                "WebLock",

                                WebLock_ContractID,

                                PR_TRUE,

                                PR_TRUE,

                                &previous);

  if (previous)

    nsMemory::Free(previous);

  return rv;

}

 

这段代码添加了主题为“content-policy”的新的分类入口点,它调用AddCategoryEntry ,与Registering for Notifications 描述的方式一样。 注销也需要一个相似的步骤。

 

Implementing the nsIContentPolicy

现在,你可以把WebLock组件放入安装的Gecko环境下。 当组件被加载后,每次Web页面加载,Gecko会调用WebLcok里的nsIContentPolicy 的实现,当load方法被调用时,它的返回的正确值会防止页面显示。

 

我们要实现的web locking策略十分简单:对每个传入的load请求,我们将确认他的URI是否在“good” URLs列表里(白名单)。

 

你可以扩展这个实现,从远端服务器获取这个白名单URLs列表,远程白名单适用于企业内部使用WebLock组件时,例如,Gecko有网络API支持这些操作。 或者,你能够实现web lock用于替代禁止任何站点,组件将简单的记录所有读取的URLs。 在任何情况下,使用 XPCOM 组件的过程是相同的。

 

处理页面加载之前的检查方法,在我们的组件里只需要关心nsIContentPolicy 接口的函数ShouldLoad()的实现。 nsIContentPolicy 的其它方法可以只禁止文档某些指定元素的加载,但我们的策略型更强:如果 URL 在白名单里,就阻止整个页面。WebLock 组件中,ShouldLoad的实现如下所示:

 

 

NS_IMETHODIMP WebLock::ShouldLoad(PRInt32 contentType,

                                  nsIURI *contentLocation,

                                  nsISupports *ctxt,

                                  nsIDOMWindow *window,

                                  PRBool *_retval)

 

Uniform Resource Locators

这个方法传入一个nsIURL类型的接口指针,它基于统一资源标识符,或者URI。 这个类型的定义参见World Wide Web Consortium

· 用于访问资源的机制的命名方案

· 承载资源的计算机的名称

· 资源的名称,作为路径给出

在上下文中,URI是一个字符串,用于引用某个地方或web上的东西。 URI被叫做统一资源定位器或者URL。 关于 URIsURLs的更多信息参见intro to the HTML 4 specification 。

 

Gecko把这些标识符封装到两个接口:nsIURI和你算IURL。 你可以在这两个几口之间调用QueryInterface。 网络库,Necko,当处理请求时,与这些接口交互。 当你想使用Necko下载一个文件时,例如,所有你提供的可能只有一个字符串,他就是表示文件的URI。当你把这个字符串传递给Necko时,他将创建一个对象,此对象至少实现了你算IURI接口(也许也实现了其它接口)。

 

目前,WebLock实现ShouldLoad方法的机制就是把参数与白名单里的每一个字符串进行比较。 我们只应该对远程的URL做这种比较,因为我们不想阻止应用程序加载他所需要的本地资源,例如,直接通过 resource://协议获取本地文件。 如果这种类型的URIs被阻止,Gecko将不能启动,因此我们把对内容的限制策略只应用与HTTPFTP协议。

 

把从nsIURI提取的字符串与白名单里的字符串进行比较,这个工作需要你自己来实现,你可以逐个与nsIURI对象进行比较,如下面章节一样。 这可以确保URLs在比较之前是规范的。

 

校验白名单(Checking the White List) 

WebLock的对于ShouldLoad方法的实现,开始于从传入的nsIURI里提取协议类型。 如果协议是“http”,“https”,或者“ftp”,它立即返回 true,将继续加载进程而不阻塞。

 

WebLock将试着阻塞上面三种类型的URI。 当URI是他们中的一种时,它会遍历链接列表,并且会为列表里的每一个URL字符串创建一个新的nsIURI对象。 从每个对象里提取hostURI进行比较。 如果匹配,组件允许继续加载,返回true。 如果不匹配,组件返回false,阻止加载。

 

URI Caching

缓存URI将会让这个方法实现更快,因为可以避免更多对象的创建和销毁。这里指出了XPCOM的一个重要缺点,你不能在栈上创建对象。 

 

在紧凑的循环中创建这么多对象,没有问题,如果保持URL的内存缓存在对象的生命周期里是有效的。 但是,无论怎么优化实现内存的使用,每个XPCOM对象的创建都会产生一次堆分配。

 

URL类型( "http", "https", 和"ftp")的字符串比较如下所示:

 

nsEmbedCString scheme;

contentLocation->GetScheme(scheme);

 

if (strcmp("http", scheme.get())  != 0 &&

    strcmp("https", scheme.get()) != 0 &&

    strcmp("ftp", scheme.get())   != 0)

{

  // this isn't a type of URI that we deal with. 

  *_retval = PR_TRUE; 

  return NS_OK; 

}

 

Creating nsIURI Objects

使用nsIIOService创建一个你算URI。 nsIIOService 是网络库“necko”的一部分,它负责关闭网络请求,管理如httpftpfile这些协议,并且创建nsIURIs。 Necko 提供了巨大的网络功能,但是WebLock组件只用到了创建nsIURI对象的功能。

 

使用 服务管理器获取nsIIOService。这个对象将会在组件的整个生命周期里使用,需要缓存下来。 一个获取nsIIOService 的地方是Observe()方法,里面已经有了一个服务管理器的指针。 获取代码如下所示:

 

// Get a pointer to the IOService

rv = servMan->GetServiceByContractID("@mozilla.org/network/io-service;1",

                                     NS_GET_IID(nsIIOService),

                                     getter_AddRefs(mIOService));

 

一旦你获取了接口指针,你可以很容易从一个字符串创建nsIURI对象,代码片段如下所示:

 

nsCOMPtr<nsIURI> uri;

nsEmbedCString urlString(node->urlString);

mIOService->NewURI(urlString,

                   nsnull,

                   nsnull,

                   getter_AddRefs(uri));

 

这段代码使用nsEmbedCString封装了一个C字符串,也许你还记得Gecko APIs有很多关于字符串的泪。 关于字符串的更多信息参见String Classes in XPCOM 。

 

一旦URL字符串封装到nsEmbedCString,它就可以传递给方法 NewURI。 这个方法解析传入的字符串然后创建一个实现了nsIURI接口的对象。 传递给NewURI的两个nsnull参数分别用于指定字符串的字符集和any base URI to use,。 我们假设这里的URL字符串的字符集是UTF-8,还假设每个URL字符串是绝对的。 关于相对URLs的更多信息,参见intro to the HTML 4 specification

 

 

下面是ShouldLoad()方法的完整实现:

 

NS_IMETHODIMP

WebLock::ShouldLoad(PRInt32 contentType,

                    nsIURI *contentLocation,

                    nsISupports *ctxt,

                    nsIDOMWindow *window,

                    PRBool *_retval)

{

  if (!contentLocation)

    return NS_ERROR_FAILURE;

 

 

  nsEmbedCString scheme;

  contentLocation->GetScheme(scheme);

 

  if (strcmp("http", scheme.get())  != 0 &&

      strcmp("https", scheme.get()) != 0 &&

      strcmp("ftp",  scheme.get())  != 0)

  {

    // this isn't a type of URI that we deal with

    *_retval = PR_TRUE;

    return NS_OK;

  }

 

  nsEmbedCString hostToLoad;

  contentLocation->GetHost(hostToLoad);

 

  // Assume failure.  Do not allow this nsIURI to load.

  *_retval = PR_FALSE;

 

  nsresult rv;

 

  nsCOMPtr<nsIServiceManager> servMan;

  rv = NS_GetServiceManager(getter_AddRefs(servMan));

  if (NS_FAILED(rv)) 

    return rv;

 

  nsCOMPtr<nsIIOService> mIOService;

  // Get a pointer to the IOService

  rv = servMan->GetServiceByContractID("@mozilla.org/network/io-service;1",

                                       NS_GET_IID(nsIIOService),

                                       getter_AddRefs(mIOService));

  if (NS_FAILED(rv))

    return rv;

 

  urlNode* node = mRootURLNode;

  PRBool match = PR_FALSE;

 

  while (node)

  {

    nsCOMPtr<nsIURI> uri;

    nsEmbedCString urlString(node->urlString);

    rv = mIOService->NewURI(urlString, nsnull,  nsnull, getter_AddRefs(uri));

 

    // if anything bad happens, just abort

    if (NS_FAILED(rv))

      return rv;

 

    nsEmbedCString host;

    uri->GetHost(host);

 

    if (strcmp(hostToLoad.get(), host.get()) == 0)

    {

      // match found.  Allow this nsIURI to load

      *_retval = PR_TRUE;

      return NS_OK;

    }

    node = node->next;

  }

  return NS_OK;

}

 

现在,所有的后端的工作都完成了。 你当然可以把这些工作做一些提升,但本章只是介绍介绍创建基本的“浏览器辅助对象”。 下一章将会介绍如何把这些与前端绑到一起—具体说来,就是如何使用XPConnect和用户界面中的JavaScript来访问和控制组件。

 

0 0
原创粉丝点击