FireFox采用Gecko内核,不同于IE内核,不能直接通过HWND像处理IE内核那样获取IHTMLDocument2。幸好Mozilla为Gecko做了一层映射,使得Gecko支持MSAA接口,可以通过HWND间接获得IHTMLDocument2(实际上是ISimpleDOMDocument,和IHTMLDocument2同样继承自IUnknown)。
网上有篇文章《基于IE和Gecko内核的网页内容获取与分析研究》,可惜这篇文章中提到的方法只对旧版的FireFox有效。搜了好久都没发现有关新版本的中文资料,许多文章都是针对FireFox3.x,并且不太靠谱。无耐之下只得研究Mozilla官网文档。
Mozilla官网文章:
《Supported MSAA Interfaces》:讲了对Gecko做了映射,以支持MSAA接口。
《MSAA Implementation Features》:讲如何如何获取网页文档信息,但不全是我们想要的,我们要做的是通过HWND的到当前标签页的网页文档信息。
《Find the Window and Load the Document》:告诉我们FireFox将变成一个单窗口的应用程序。最终查找顶层UI窗口并加载网页文档(ISimpleDOMDocument)的唯一方法是使用“accessible relation NAVRELATION_EMBEDS”。我使用SPY++查看FireFox9的窗口时确实如此,只有主界面一个窗口,窗口类为MozillaWindowClass。这篇文章修改日期是“20:42, 4 Mar 2008”,因此这个方法在很早的版本中就已经采用了,可是文中资料却鲜有提及。
《MSAA Relations》:定义了上面提到的NAVRELATION_EMBEDS的值。
参考上面三篇文章和其他一些参考资料有了下面获取网页文档的方法:
1、从Mozilla Developer Center下载三个文件ISimpleDOMNode.idl、ISimpleDOMText.idl 、ISimpleDOMDocument.idl,《MSAA Implementation Features》下有提供下载链接。
2、使用微软的IDL编译器即MIDL(位于VC6安装目录下的VC98\Bin中)来生成Windows平台下的头文件,命令格式如下:
MIDL ISimpleDOMNode.idl
MIDL ISimpleDOMText.idl
MIDL ISimpleDOMDocument.idl
然后我们就得到了9个文件,其中只需要6个:ISimpleDOMNode.h、ISimpleDOMNode_i.c、ISimpleDOMText.h、ISimpleDOMText_i.c、ISimpleDOMDocument.h、ISimpleDOMDocument_i.c
3、假设已经得到到FireFox窗口句柄了,以获取网页URL为例,代码如下:
#include <windows.h>
#include <objbase.h>
#include <atlbase.h>
#include <oleacc.h>
#include <comutil.h>
#include "ISimpleDOMNode.h"
#include "ISimpleDOMText.h"
#include "ISimpleDOMDocument.h"
////////////////////////////////////////////////////////////////////////////////
//
// NAVRELATION_EMBEDS 要自己定义,其值在《MSAA Relations》中给出
#ifndef NAVRELATION_EMBEDS
#define NAVRELATION_EMBEDS 0x1009
#endif
////////////////////////////////////////////////////////////////////////////////
//
bool GetUrl_Gecko(HMODULE hOleAccDll, HWND hWndBrowser, LPTSTR pszUrl, size_t stBytes)
{
if (NULL == hOleAccDll || NULL == hWndBrowser)
{
return false;
}
TCHAR szClassName[_MAX_PATH] = {0};
::GetClassName(hWndBrowser, szClassName, sizeof(szClassName));
if (_tcsncmp(szClassName, TEXT("MozillaWindowClass"), sizeof(szClassName)) != 0)
{
return false;
}
IAccessible* pAccBrowser = NULL;
LPFNACCESSIBLEOBJECTFROMWINDOW pfAccessibleObjectFromWindow
= (LPFNACCESSIBLEOBJECTFROMWINDOW)::GetProcAddress(
hOleAccDll, "AccessibleObjectFromWindow");
if (NULL == pfAccessibleObjectFromWindow)
{
return false;
}
HRESULT hr = pfAccessibleObjectFromWindow(
hWndBrowser, OBJID_CLIENT, IID_IAccessible, (void**)&pAccBrowser);
if (FAILED(hr) || NULL == pAccBrowser)
{
return false;
}
VARIANT vtStart;
VARIANT vtResult;
vtStart.vt = VT_I4;
vtResult.lVal = CHILDID_SELF;
pAccBrowser->accNavigate(NAVRELATION_EMBEDS, vtStart, &vtResult);
IDispatch* pDisp = vtResult.pdispVal;
if (NULL == pDisp)
{
return false;
}
IAccessible* pAccDoc = NULL;
hr = pDisp->QueryInterface(IID_IAccessible, (void**)&pAccDoc);
if (FAILED(hr) || NULL == pAccDoc)
{
return false;
}
IServiceProvider *pServProv = NULL;
hr = pAccDoc->QueryInterface(IID_IServiceProvider, (void**)&pServProv);
if (FAILED(hr) || NULL == pServProv)
{
return false;
}
const GUID refguid = {0x0c539790, 0x12e4, 0x11cf, 0xb6, 0x61,
0x00, 0xaa, 0x00, 0x4c, 0xd6, 0xd8};
ISimpleDOMNode* pNode = NULL;
hr = pServProv->QueryService(refguid, IID_ISimpleDOMNode, (void**)&pNode);
if (FAILED(hr) || NULL == pNode)
{
return false;
}
ISimpleDOMDocument* pDoc = NULL;
hr = pNode->QueryInterface(IID_ISimpleDOMDocument, (void**)&pDoc);
if (FAILED(hr) || NULL == pDoc)
{
return false;
}
BSTR bstrUrl = NULL;
hr = pDoc->get_URL(&bstrUrl);
if (FAILED(hr) || NULL == bstrUrl)
{
return false;
}
_tcsncmp(pszUrl, _bstr_t(bstrUrl), stBytes);
return true;
}
////////////////////////////////////////////////////////////////////////////////
//
bool GetWebPageUrl(HWND hWndBrowser, LPTSTR pszUrl, size_t stBytes)
{
if (!::IsWindow(hWndBrowser))
{
return false;
}
::CoInitialize(NULL);
// 显示加载 MSAA 以便确定是否已安装
HMODULE hOleAccDll = ::LoadLibrary(TEXT("OLEACC.DLL"));
if (NULL == hOleAccDll)
{
::CoUninitialize();
return false;
}
bool bSucceeded = ::GetUrl_Gecko(hOleAccDll, hWndBrowser, pszUrl, stBytes);
::FreeLibrary(hOleAccDll);
::CoUninitialize();
return bSucceeded;
}