CEF中访问修改HTML DOM元素

有时你可能想在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();

        do
        {
            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();
        do
        {
            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 };
        do{
            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>
<html>
  <!--
  Copyright (c) 2016 foruok(微信订阅号“程序视界”)
  -->
    <style type="text/css">
    #divtest
    {
        position: relative;
        left: 30px;
        top: 10px;
        padding: 10px;
        width: 300px;
        height: 200px;
        background-color: gray;
    }
    </style>
<head>
    <script type="text/javascript">
      function test(){
        window.DomVisitTest();
      }
    </script>
    <title>Dom Visit Test</title>
</head>

<body>
<h1 id="hello">Hello</h1>
<form>
  <input  type="button" value="VisitDom" onclick="test()"/>
</form>
one<br>two
<p>This is text</p>
<div id="divtest">
  <p>div hello</p>
</div>
</body>
</html>

你可能注意到我给VisitDom按钮关联的test()方法内调用了window.DomVisitTest()方法,该方法是我在C++代码中绑定到window对象上的,参考CEF中JavaScript与C++交互



就这样吧。

其他参考文章详见我的专栏:【CEF与PPAPI开发】。

时间: 2024-10-11 03:52:20

CEF中访问修改HTML DOM元素的相关文章

使用jQuery匹配文档中所有的li元素,返回一个jQuery对象,然后通过数组下标的方式读取jQuery集合中第1个DOM元素,此时返回的是DOM对象,然后调用DOM属性innerHTML,读取该元素 包含的文本信息

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><head> <meta htt

Jquery 方式获取 iframe Dom元素

Jquery 方式获取 iframe Dom元素 测试页面代码: <html>  <head>   <title>jquery方式,访问iframe页面dom元素</title>   <meta name="Author" content="孙勤波">   <meta http-equiv="Content-Type" content="text/html;charset

让DOM元素自动滚到视野内ScrollIntoView

概述 项目中需要把一个DOM元素自动滚动到视野内,百思不得其解,最后再element库里面发现了这个方法,记录下来供以后开发时参考,相信对其他人也有用. 参考资料:element scroll-into-view.js 代码 代码如下: /* eslint-disable no-param-reassign */ export default function scrollIntoView(container, selected) { if (!selected) { container.scr

JS中选择DOM元素的方法集锦

各种选取元素的方法的速度,用原生的方法比jQuery要快差不多8倍,IE8是最慢的,IE9的速度差不多是IE8的3倍,Chrome的表现最好,其次是Firefox 选取文档元素的方法: 1.通过ID选取元素(getElementById)   1)使用方法:document.getElementById("domId")        其中,domId为要选取元素的id属性值   2)兼容性:低于IE8版本的IE浏览器对getElementById方法的实现是不区分元素ID号的大小写的

Dom元素中的元素应用

1.创建与修改table元素: 创建表格:var a=document.createElement("table"); 创建表格中的tbody,并且添加入表格a中:var b=document.createElemrnt("tbody");a.appendChild(b); 同理即可以创建添加出 行tr 列td: 另一种方法: b.insertRow(0); //创建第一行 b.rows[0].insertCell(0); //在第一行中创建第一列 b.rows[0

jQuery -&gt; 如何【先创建、再修改、后添加】 DOM元素

如何一气呵成地,on the fly地操作DOM元素呢? 例如顺序执行[创建]-> [修改]-> [添加]三个动作. 由于jQuery支持链式操作,其实就是设计模式的builder模式,所以我们可以把三个操作串在一起来执行. 首先创建一个p元素,内容包含一个a元素. $('<p><a>jQuery</a></p>') 然后为a元素添加一个href属性 $('<p><a>jQuery</a></p>'

动态修改 dom 元素的伪类样式

最近写代码,需要修改伪类的 content 属性,不想定义两个样式进行切换,而是直接通过 js 进行修改. html 中的伪类(如 a:hover / a:link / class::before / clss::after 等),因为不是 dom 元素,所以 不能直接通过 js 修改其属性,虽然可以通过 切换 dom 元素的 css 样式名进行修改,但是这样就比较复杂了. 参考了一个 帖子 然后进行了一些修改,大概思路如下: 这里使用  windows 8 的工程,js 代码直接写在 defa

javascript中通过className灵活查找元素 例如我们要把根据class来进行修改样式

原文:javascript中通过className灵活查找元素 例如我们要把根据class来进行修改样式 一.背景:一个表单中,要修改一些li中有class=box的样式,将它的background设置为red红色.一般的做法是我们可以先找到父级元素 ,然后由父级元素找到所有相关tagName,最后,来一个if判断,如果class属性为box,则修改之 var oUl = document.getElementById("ul1");                          

JQueryUI-拖动(Draggable)-在DOM 元素中约束运动

定义和用法 通过定义 draggable 区域的边界来约束每个 draggable 的运动,使用 containment 选项来指定一个父级的 DOM 元素或者一 个 jQuery 选择器 示例 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge&