C++ Firefox 组件拦截/操作 HTML DOM
本文使用一个简单的例子展示如何构建一个拦截/操作 DOM 的 Firefox 组件。
背景
我已经开发 BHO 组件几个月了。开发 BHO 组件很有趣。相比之下,Firefox 似乎非常令人沮丧。我认为第一个原因是,缺乏可供参考的示例和代码。其次,Mozilla 没有像微软那样提供足够的文档。嗯,我并不责怪他们。毕竟,这是一个小公司,而且是开源的。
在接下来的章节中,我将通过一个例子向您展示如何拦截 Firefox 的 DOM 元素并像在 BHO 中那样操作它们。
强烈建议您在继续阅读之前,先阅读以下两篇文章。
- http://www.iosart.com/firefox/xpcom/: 创建 XPCOM 组件
- http://www.borngeek.com/firefox/toolbar-tutorial/: Firefox 工具栏教程
第一篇文章教您如何构建一个组件;第二篇文章教您如何制作一个扩展。
这里的示例基于第一篇文章中的示例。
我花了整整两周时间来学习 Firefox 和 XPCOM 的工作原理。我将使用这个简单的图表来展示其结构。
UI--> XPConnect--> XPCOM
您所见过和可以下载的大多数附加组件都是扩展,例如工具栏等。其中大部分是用 JavaScript 编写的。如果您不想接触一些高级组件,例如仅用于导航窗口的工具栏等,这也没关系。但是,如果您想做得更深入,就必须调用在 XPConnect 或 XPCOM 下构建的组件。
以下代码是来自 Mozilla 官方教程的一个简单的 Cookie 管理器。
var cookiemanager = Components.classes["@mozilla.org/cookiemanager;1"].getService();
cookiemanager = cookiemanager.QueryInterface(Components.interfaces.nsICookieManager);
// called as part of a largerDeleteAllCookies() function
function FinalizeCookieDeletions() {
for (var c=0; c<deletedCookies.length; c++) {
cookiemanager.remove(deletedCookies[c].host, deletedCookies[c].name,
deletedCookies[c].path);
}
deletedCookies.length = 0;
}
理解这个过程相当容易。
- 获取组件服务
- 查询组件
- 调用组件下的方法
这部分技术含量不高。但是,组件的工作原理是另一回事。
Firefox 基于 Gecko SDK。基本上,所有以 "ns" 开头的 API,例如 nsIWebBrowser
(类似于 BHO 中的 IWebBrowser
),都来自 Gecko SDK。您可以在 www.xulplanet.com 上找到更多信息。
切入正题。现在,我想为 Firefox 构建一个 FormFiller。同时,由于以下原因,我不想使用 JavaScript 来完成这项工作:
- JavaScript 会暴露您的代码。
- JavaScript 的功能非常有限。
因此,这里有两种解决方案:
- 使用 C++ 构建 Firefox 扩展
- 使用 C++ 构建 Firefox 组件
我选择了第二种。尽管在 Firefox 上注册组件比注册扩展要复杂得多。但调用核心组件和操作数据时,它更加安全。
如何做
与 BHO 类似,有一个 nsIWebBrowser
,它提供了任何基于 XPCOM 的浏览器(如 SeaMonkey 或 NetScape)的 API。通过 nsIWebBrowser
,我们可以获取 nsIDOMWindow
-> nsIDOMDocument
-> nsIDOMElement
,然后填写我们想要的信息。但是,这在 Firefox 中是**不**起作用的。原因很简单:Firefox 不使用 nsIWebBrowser
。当您通过组件管理器调用 nsIWebBrowser
时,它会返回一些随机的无用数据。
我不知道他们为什么这样做。也许是因为 Firefox 的多标签结构。所以我们的问题就在这里。我们必须在继续之前初始化 nsIDOMWindow
。
为了节省您在 Google 上搜索信息的时间,请允许我提供这个简单的函数原型。
var res = obj.Nothing(window);
其中,"Nothing
" 是用于填充表单的方法。"window
" 是 JavaScript 自然拥有的浏览器窗口对象。
所以,在您的代码内部,您将方法声明为:
NS_IMETHOD Nothing(nsIDOMWindow *domWindow); //.h file
NS_IMETHODIMP MyComponent::Nothing(nsIDOMWindow *domWindow) //cpp file
当 JavaScript 将窗口传递给方法时,它会自动转换为 nsIDOMWindow
。
好的,一半的工作已经完成了。
另一半很简单。看看下面的代码,您就会明白了。
NS_IMETHODIMP MyComponent::Nothing(nsIDOMWindow *domWindow)
{
nsEmbedString temp(L"test");
nsEmbedString attribute(L"value");
nsEmbedString id(L"id");
nsEmbedString value(L"value");
//The above declare some strings.
/*
Please note that the string in XPCOM is declared as either nsAString
or nsString or nsEmbedString Please refer to
https://mdn.org.cn/en/docs/XPCOM:Strings for more information
*/
nsIDOMWindow* window; //you can use the parameter directly
window=domWindow;
nsCOMPtr<nsIDOMDocument> domDocument;
nsCOMPtr<nsIDOMElement> element;
window->GetDocument(getter_AddRefs(domDocument)); //Get document
domDocument->GetElementById(temp,getter_AddRefs(element));
//Find target element, you also can use GetElementByTagName
//to get a node list. Then loop through the node list.
//Please refer to XULPlanet for more info
//about nsIDOMDocument
element->GetAttribute(id,value);
//I get the value of "id" attribute
//and fill it into "value attribute"
element->SetAttribute(attribute,value);
return NS_OK;
}
好的,以上就是您需要的所有代码了。
如何构建项目
我强烈推荐 http://www.iosart.com/firefox/xpcom/。我从那里学到了关于 XPCOM 组件的大部分知识。
要开始您的项目,您需要一个 .idl 文件。
#include "nsISupports.idl"
#include "nsIDOMWindow.idl"
[scriptable, uuid(_YOUR_INTERFACE_GUID_)]
interface IMyComponent : nsISupports
{
void Nothing(in nsIDOMWindow domWindow);
};
然后下载 XPIDL.exe 来生成 .h 文件。
引自文章:
- xpidl -m header -I_DIR_ IMyComponent.idl 将创建 IMyComponent.h 头文件。
- xpidl -m typelib -I_DIR_ IMyComponent.idl 将创建 IMyComponent.xpt 类型库文件。
然后按照 http://www.iosart.com/firefox/xpcom/ 的说明进行操作。
调用组件
有两种方法可以调用该组件:
- 通过扩展
- 从页面上的 JavaScript 调用
都一样。扩展是一个带有 JavaScript 的 XUL。请参考 http://www.borngeek.com/firefox/toolbar-tutorial/ 来了解其工作原理。
下面的 HTML 页面代码调用了该组件。
<HTML>
<SCRIPT type="text/javascript">
function MyComponentTestGo() {
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
const cid = "@mydomain.com/XPCOMSample/MyComponent;1";
obj = Components.classes[cid].createInstance();
obj = obj.QueryInterface(Components.interfaces.IMyComponent);
} catch (err) {
alert(err);
return;
}
var res = obj.Nothing(window);}
</SCRIPT>
<BODY>
<BUTTON ONCLICK="MyComponentTestGo();" id="go">Go</BUTTON>
<input id="test" value="" />
</BODY>
</HTML>
结论
开发 Firefox 组件最大的问题是缺乏文档和代码。即使是 XPCOM 本身也存在同样的问题。但我认为情况会越来越好。通常,Mozillzine 上有一些有经验的人可以寻求帮助。
我希望这篇简单的文章能对那些在开发 Firefox 时感到沮丧的人有所帮助。如果您有任何评论或问题,请在此处留言。我将尽我所能回复您。