有时你可能想在C++代码中直接操作HTML中的某个元素,比如改变某个按钮的状态(文字、颜色)等,此时可以使用CEF提供的CefDomVisitor、CefDOMDocument、CefDomNode这三个类,包含cef_dom.h即可。
我们可以用它们完成下列任务:
-
使用DOM模型访问HTML的各种节点(Element、Attribute、Text、CDATA、Comment、Document等)
-
修改某个元素的属性
-
修改某个Text节点的值
下面简要说说各个类的用法。
CefDOMDocument
CefDOMDocument对应JS里的document,不过功能少一些,类声明如下:
class CefDOMDocument : public virtual CefBase {
public:
typedef cef_dom_document_type_t Type;
// Returns the document type.
/*--cef(default_retval=DOM_DOCUMENT_TYPE_UNKNOWN)--*/
virtual Type GetType() =0;
// Returns the root document node.
/*--cef()--*/
virtual CefRefPtr<CefDOMNode> GetDocument() =0;
// Returns the BODY node of an HTML document.
/*--cef()--*/
virtual CefRefPtr<CefDOMNode> GetBody() =0;
// Returns the HEAD node of an HTML document.
/*--cef()--*/
virtual CefRefPtr<CefDOMNode> GetHead() =0;
// Returns the title of an HTML document.
/*--cef()--*/
virtual CefString GetTitle() =0;
// Returns the document element with the specified ID value.
/*--cef()--*/
virtual CefRefPtr<CefDOMNode> GetElementById(const CefString& id) =0;
// Returns the node that currently has keyboard focus.
/*--cef()--*/
virtual CefRefPtr<CefDOMNode> GetFocusedNode() =0;
// Returns true if a portion of the document is selected.
/*--cef()--*/
virtual bool HasSelection() =0;
// Returns the selection offset within the start node.
/*--cef()--*/
virtual int GetSelectionStartOffset() =0;
// Returns the selection offset within the end node.
/*--cef()--*/
virtual int GetSelectionEndOffset() =0;
// Returns the contents of this selection as markup.
/*--cef()--*/
virtual CefString GetSelectionAsMarkup() =0;
// Returns the contents of this selection as text.
/*--cef()--*/
virtual CefString GetSelectionAsText() =0;
// Returns the base URL for the document.
/*--cef()--*/
virtual CefString GetBaseURL() =0;
// Returns a complete URL based on the document base URL and the specified
// partial URL.
/*--cef()--*/
virtual CefString GetCompleteURL(const CefString& partialURL) =0;
如你所见,它能获取一些字符串值(URL、标题等),能根据id查找某个元素(在JS里我们最常用的方式),能返回Document、Head、Body等节点,这些节点的类型是CefDOMNode。
注意这个类的方法只能在Renderer进程的主线程上调用(TID_RENDERER)。
CefDOMNode
在 HTML DOM (文档对象模型)中,每个部分都是节点:
- 文档本身是文档节点
- 所有 HTML 元素是元素节点
- 所有 HTML 属性是属性节点
- HTML 元素内的文本是文本节点
- 注释是注释节点
CefDOMNode代表了一个HTML DOM节点,它的声明如下:
class CefDOMNode : public virtual CefBase {
public:
typedef std::map<CefString, CefString> AttributeMap;
typedef cef_dom_node_type_t Type;
// Returns the type for this node.
/*--cef(default_retval=DOM_NODE_TYPE_UNSUPPORTED)--*/
virtual Type GetType() =0;
// Returns true if this is a text node.
/*--cef()--*/
virtual bool IsText() =0;
// Returns true if this is an element node.
/*--cef()--*/
virtual bool IsElement() =0;
// Returns true if this is an editable node.
/*--cef()--*/
virtual bool IsEditable() =0;
// Returns true if this is a form control element node.
/*--cef()--*/
virtual bool IsFormControlElement() =0;
// Returns the type of this form control element node.
/*--cef()--*/
virtual CefString GetFormControlElementType() =0;
// Returns true if this object is pointing to the same handle as |that|
// object.
/*--cef()--*/
virtual bool IsSame(CefRefPtr<CefDOMNode> that) =0;
// Returns the name of this node.
/*--cef()--*/
virtual CefString GetName() =0;
// Returns the value of this node.
/*--cef()--*/
virtual CefString GetValue() =0;
// Set the value of this node. Returns true on success.
/*--cef()--*/
virtual bool SetValue(const CefString& value) =0;
// Returns the contents of this node as markup.
/*--cef()--*/
virtual CefString GetAsMarkup() =0;
// Returns the document associated with this node.
/*--cef()--*/
virtual CefRefPtr<CefDOMDocument> GetDocument() =0;
// Returns the parent node.
/*--cef()--*/
virtual CefRefPtr<CefDOMNode> GetParent() =0;
// Returns the previous sibling node.
/*--cef()--*/
virtual CefRefPtr<CefDOMNode> GetPreviousSibling() =0;
// Returns the next sibling node.
/*--cef()--*/
virtual CefRefPtr<CefDOMNode> GetNextSibling() =0;
// Returns true if this node has child nodes.
/*--cef()--*/
virtual bool HasChildren() =0;
// Return the first child node.
/*--cef()--*/
virtual CefRefPtr<CefDOMNode> GetFirstChild() =0;
// Returns the last child node.
/*--cef()--*/
virtual CefRefPtr<CefDOMNode> GetLastChild() =0;
// The following methods are valid only for element nodes.
// Returns the tag name of this element.
/*--cef()--*/
virtual CefString GetElementTagName() =0;
// Returns true if this element has attributes.
/*--cef()--*/
virtual bool HasElementAttributes() =0;
// Returns true if this element has an attribute named |attrName|.
/*--cef()--*/
virtual bool HasElementAttribute(const CefString& attrName) =0;
// Returns the element attribute named |attrName|.
/*--cef()--*/
virtual CefString GetElementAttribute(const CefString& attrName) =0;
// Returns a map of all element attributes.
/*--cef()--*/
virtual void GetElementAttributes(AttributeMap& attrMap) =0;
// Set the value for the element attribute named |attrName|. Returns true on
// success.
/*--cef()--*/
virtual bool SetElementAttribute(const CefString& attrName,
const CefString& value) =0;
// Returns the inner text of the element.
/*--cef()--*/
virtual CefString GetElementInnerText() =0;
注意这个类的方法只能在Renderer进程的主线程上调用(TID_RENDERER)。
结合对HTML DOM节点的理解以及上面的代码,就能理解我们能使用CefDOMNode做什么:
- 使用IsXXX或GetType判断节点类型
- 使用GetNextSibling、GetPreviousSibling遍历兄弟节点
- 如果是Text节点(叶子节点),SetValue可以改变其文本
- 如果是Element节点,可以使用GetFirstChild、GetLastChild获取孩子,使用SetElementAttribute(s)改变属性,使用GetElementAttibute(s)获取属性
HTML DOM中的Element,有appendChild、insertBefore等方法,可以很方便地动态插入节点改变DOM和网页展示效果,而这个CefDOMNode就没有相应的方法,好像不太方便……
CefDOMVisitor
这个类的声明如下:
class CefDOMVisitor : public virtual CefBase {
public:
// Method executed for visiting the DOM. The document object passed to this
// method represents a snapshot of the DOM at the time this method is
// executed. DOM objects are only valid for the scope of this method. Do not
// keep references to or attempt to access any DOM objects outside the scope
// of this method.
/*--cef()--*/
virtual void Visit(CefRefPtr<CefDOMDocument> document) =0;
要访问或修改HTML DOM,就必须实现这个类,然后将其对象传递给CefFrame::VisitDOM(CefRefPtr visitor)方法,最后你的Visit方法就被调用来访问或修改HTML DOM。
看一个简单的实现,显示DomVisitTestor类的声明:
class DomVisitTestor : public CefDOMVisitor
public:
DomVisitTestor();
void TestAccess(CefRefPtr<CefDOMDocument> document);
void TestModify(CefRefPtr<CefDOMDocument> document);
void Visit(CefRefPtr<CefDOMDocument> document) OVERRIDE;
IMPLEMENT_REFCOUNTING(DomVisitTestor);
然后是DomVisitTestor的实现:
void DomVisitTestor::TestAccess(CefRefPtr<CefDOMDocument> document)
OutputDebugStringW(L"DomVisitTestor::TestAccess\r\n");
OutputDebugStringW(document->GetTitle().ToWString().c_str());
OutputDebugStringW(document->GetBaseURL().ToWString().c_str());
CefRefPtr<CefDOMNode> headNode = document->GetHead();
OutputDebugStringW(headNode->GetName().ToWString().c_str());
OutputDebugStringW(headNode->GetAsMarkup().ToWString().c_str());
wchar_t szLog[512] = { 0 };
if (headNode->HasChildren())
CefRefPtr<CefDOMNode> childNode = headNode->GetFirstChild();
swprintf_s(szLog, 256, L"node name -%s, type-%d, value-%s\r\n",
childNode->GetName().ToWString().c_str(), childNode->GetType(), childNode->GetValue());
OutputDebugStringW(szLog);
} while ( (childNode = childNode->GetNextSibling()).get() );
CefRefPtr<CefDOMNode> bodyNode = document->GetBody();
OutputDebugStringW(bodyNode->GetAsMarkup().ToWString().c_str());
if (bodyNode->HasChildren())
CefRefPtr<CefDOMNode> childNode = bodyNode->GetFirstChild();
swprintf_s(szLog, 256, L"node name -%s, type-%d, value-%s\r\n",
childNode->GetName().ToWString().c_str(), childNode->GetType(), childNode->GetValue());
OutputDebugStringW(szLog);
} while ((childNode = childNode->GetNextSibling()).get());
void DomVisitTestor::TestModify(CefRefPtr<CefDOMDocument> document)
OutputDebugStringW(L"DomVisitTestor::TestModify\r\n");
CefRefPtr<CefDOMNode> bodyNode = document->GetBody();
if (bodyNode->HasChildren())
CefRefPtr<CefDOMNode> childNode = bodyNode->GetFirstChild();
wchar_t szLog[512] = { 0 };
swprintf_s(szLog, 256, L"node name -%s,tagName-%s type-%d, value-%s\r\n",
childNode->GetName().ToWString().c_str(),
childNode->GetElementTagName().ToWString().c_str(),
childNode->GetType(), childNode->GetValue());
OutputDebugStringW(szLog);
if (childNode->IsElement() && childNode->GetElementTagName() == "H1"
&& childNode->GetElementAttribute("id") == "hello")
CefRefPtr<CefDOMNode> textNode = childNode->GetFirstChild();
swprintf_s(szLog, 512, L"found hello, text - %s\r\n", textNode->GetValue().ToWString().c_str());
OutputDebugStringW(szLog);
textNode->SetValue("Hello World Modified!");
break;
} while ((childNode = childNode->GetNextSibling()).get());
CefRefPtr<CefDOMNode> hello = document->GetElementById("hello");
if (hello.get())
hello->SetElementAttribute("align", "center");
OutputDebugStringW(L"Change hello align\r\n");
void DomVisitTestor::Visit(CefRefPtr<CefDOMDocument> document)
TestAccess(document);
TestModify(document);
注意,这个类的方法也应当在Renderer进程的主线程(TID_RENDERER)上使用。
测试用的HTML文件如下:
<!DOCTYPE html>
<style type="text/css">
#divtest
position: relative;
left: 30px;
top: 10px;
padding: 10px;
width: 300px;
height: 200px;
background-color: gray;
</style>
<script type="text/javascript">
function test(){
window.DomVisitTest();
</script>
<title>Dom Visit Test</title>
</head>
<h1 id="hello">Hello</h1>
<input type="button" value="VisitDom" onclick="test()"/>
</form>
one<br>two
<p>This is text</p>
<div id="divtest">
<p>div hello</p>
</body>
</html>
你可能注意到我给VisitDom按钮关联的test()方法内调用了window.DomVisitTest()方法,该方法是我在C++代码中绑定到window对象上的,参考CEF中JavaScript与C++交互。
就这样吧。
其他参考文章详见我的专栏:【CEF与PPAPI开发】。
有时你可能想在C++代码中直接操作HTML中的某个元素,比如改变某个按钮的状态(文字、颜色)等,此时可以使用CEF提供的CefDomVisitor、CefDOMDocument、CefDomNode这三个类
一、环境搭建
1、教程:https://kefong.blog.csdn.net/article/details/119910984
2、源码:https://gitee.com/kefong/mfccef.git
以下所有工作,都是在以上源码基础上进行。
二、在browser中重写网页加载完毕执行方法 CefLoadHandler->OnLoadEnd
1、[CCefBrowserEventHandler.h] CCefHandler中增加继承CefLoadHandler
CCefBrowser
CEF3中访问修改HTML DOM元素一、调用时机的选择二、Render和Browser进程通信,获取DOM三、Render进程调试方法
例子是在 cefsimple 下进行操作的
想要在CEF3中访问DOM元素,只需要一句代码就可以得到DOM
browser->GetMainFrame()->VisitDOM(new DomVisitTestor());
但是这一句代码放在那里执行还是比较有讲究的。
这里的DomVisitTestor是继承自CefDOMVisitor的类,起什么名字都可
说明:由于很多文件无法获取到完整内容,再者具体文件内容在Filter里面进行了控制,而Fileter的内容依赖于IRequestHandler所以,外部只能操作Handler得到数据。 所以需要在,Filter和Hanlder类中,使用事件来传递具体的内容。代码如下。 Filter类如下:
public class TestImageFilter : IResponseFilter
2、下载CEF4Delphi相关的环境(dll、pak等文件),有两个版本win32【D7】、win64【fmx】.
3、解压CEF4Delphi到3rd组件文件夹下【根据个人情况】
4、解压cef_binary_3.3626.1891.g52be333_windows32中Release文件夹和Resources文件夹
5、将Release文件夹和Resources文件夹中的所有文件复制到CEF4Delphi中的bin文件夹下面
CEF (Chromium Embedded Framework) 是一个基于 Chromium 内核的开源库,可以让开发者在自己的应用程序中嵌入浏览器。
CEF 的最新版本为 CEF 88.0.4324.104,于 2021 年 12 月 16 日发布。这一版本基于 Chromium 88.0.4324.104,包括了对 Chromium 的更新和一些 CEF 特有的改进。
CEF 88.0.4324.104 中包含了一些新特性,例如:
- 支持更多的触摸事件,包括 touchcancel 和 touchenter,以及对长按、滑动和滚动事件的支持。
- 增加了对 <datalist> 元素的支持,这样可以让开发者在表单中提供自动完成建议。
- 支持在网络请求中设置自定义标头,以便更好地控制网络通信。
- 增强了对 CSS 样式的支持,包括新的属性、选择器和伪类。
此外,CEF 88.0.4324.104 还包括了一些修复和性能改进,使得 CEF 更加稳定和高效。
如果您想使用 CEF,可以在其官网(https://cef.dev/)上下载最新版本并获取更多信息。
QtAndroid详解(1):QAndroidJniObject
meraki:
华为的OD,值得去吗?
MISAYAONE:
华为的OD,值得去吗?
梦想橡皮擦:
freeSWITCH安装、配置与局域网测试
weixin_44853750: