CEF中访问修改HTML DOM元素

来源:互联网 发布:mac安装sublime text2 编辑:程序博客网 时间:2024/05/21 10:18

有时你可能想在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开发】。

0 1
原创粉丝点击