QT integrated cef10 JavaScript and C + + intermodulation

Why do we use CEF? In many cases, it is to realize the mutual call between JavaScript and native C + +. That is, when JavaScript in the web page is called, it triggers the execution of local C + + code, such as accessing hardware and other functions that JavaScript cannot complete. The local C + + code can also call back JavaScript. For example, after receiving some notifications from the operating system, the local code will transfer the notification content to the JavaScript code for execution.

In CEF, JavaScript code is running in Renderer, which is very important to understand. The place where native code is written is inappropriate, and JavaScript code cannot work normally when called. CEF uses the V8 JS engine to execute internal JS. Each Frame has its own JS Context in the browser process, and they all run in the Renderer process.

How can we use native C + + code to extend JavaScript execution? Mainly through two methods in CefRenderProcessHandler:

  • virtual void OnWebKitInitialized() {}

    After the webkit is initialized. In this function, we can register the "mapping" relationship between JavaScript and C + + code through the cefregistereextension() function, which is officially called extension

  • virtual void OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context) {}

    After the JavaScript context is created, in this function, we can bind properties and methods for window objects in JavaScript. Officially, this method is called window binding

Either way, it must be done in the rendering process.

The code in this section is completed in the QyRender project, Reference documents

1. Extension

Here is a demonstration of how to extend AES encryption and decryption for JavaScript. This section uses QT-AES This open source library is used to realize AES encryption and decryption.

1.1 prepare QT-AES

Download to GitHub QT-AES Source code, just qaesencryption.cpp and qaesencryption.h These two files can be copied into our project.

1.2 QyAppRenderer class

Qyapprender inherits from CefApp and CefRenderProcessHandler and needs to override the OnWebKitInitialized method:

// QyAppRenderer.h

class QyAppRenderer :public CefApp, public CefRenderProcessHandler {
public:
	QyAppRenderer();
    // ...  ellipsis
	void OnWebKitInitialized() OVERRIDE;
    // ...  ellipsis
};


// QyAppRenderer.cpp
void QyAppRenderer::OnWebKitInitialized() {
	qDebug() << "=====OnWebKitInitialized=======";
}

1.3 expansion

In the OnWebKitInitialized method, we need to complete the extension through the following steps:

1.3. 1 define JS text

Define a JavaScript text string in which native function is declared. In this example, three methods are demonstrated:

  • app.encrypt(originText) / / encrypt. The parameter is the string to be encrypted
  • app.decrypt(encryptText) / / decrypt. The parameter is the ciphertext string to decrypt
  • app.sayHello(callback) / / the parameter is a callback function callback =function(data) {...}
var app;
if(!app){
    app={};
    (function(){
        app.encrypt=function(originText){
            native function encrypt();//Declare local method name
            return encrypt(originText); //Execute local method
        }
        app.decrypt=function(encryptText){
            native function decrypt();//Declare local method name
            return decrypt(encryptText); //Execute local method
        }
        app.sayHello=function(callback) {
            native function sayHello();//Declare local method name
            return sayHello(callback);//Execute local method
        }
    })(); //JavaScript self executing function
}

If this JavaScript code is defined as a string and the format is not beautiful, put it into the resource file of QT project and read it from the resource file when using it:

First, create a folder resources under the project QyRender, and then create extension in this folder_ js. JS file, the content is the above JavaScript code:

Then add a QT resource file: extension_ js. qrc

Add extension_ js. JS file is added to the resource file

C++ 11 provides the syntax of multi line strings, which can also be used to directly define JavaScript code into strings.

	std::string jsCode = R"(
		var app;
        if(!app){
			app={};
			....
        }
    )";

1.3. 2. Implement CefV8Handler interface

The native code needs to implement the CefV8Handler interface. For convenience, it is written directly in qyapprender Cpp file:

// QyAppRenderer.cpp
#pragma execution_character_set("UTF-8")
#include "QyAppRenderer.h"
#include <QDebug>
#include <QProcess>
#include "encrypt/qaesencryption.h"
#include <QCryptographicHash>
#include <QDateTime>
namespace {
}
class AppNativeV8Handler : public CefV8Handler {
	public:
		AppNativeV8Handler() {}
		bool Execute(const CefString& name, //JavaScript function name
			CefRefPtr<CefV8Value> object,     //JavaScript function holder
			const CefV8ValueList& arguments,//JavaScript parameters
			CefRefPtr<CefV8Value>& retval,  // JavaScript return value
			CefString& exception) override {
			// encryption
			if (name == "encrypt") {
				std::string originText = arguments.at(0).get()->GetStringValue();
				QString	 rtText = encrypt(QString::fromStdString(originText));
				//Return value to retval
				retval = CefV8Value::CreateString(rtText.toStdString());
				return true;
			}
			// decrypt
			if (name == "decrypt") {
				std::string encryptText = arguments.at(0).get()->GetStringValue();
				QString	 rtText = decrypt(QString::fromStdString(encryptText));
				retval = CefV8Value::CreateString(rtText.toStdString());
				return true;
			}
			// sayHello JavaScript function with callback
			// The name is sayHello, the number of parameters is 1, and the type is a function
			if (name == "sayHello" && arguments.size() == 1 && arguments[0]->IsFunction()) {
				// Get callback function
				CefRefPtr<CefV8Value> callback = arguments[0];
				QDateTime now = QDateTime::currentDateTime();//Get the current system time
				time_t nowTime = now.toTime_t();
				//Convert to CefDate type
				CefTime cefNow(nowTime);
				CefRefPtr<CefV8Value> callbackFunctionParam = CefV8Value::CreateDate(cefNow);

				CefV8ValueList arguments;
				arguments.push_back(callbackFunctionParam);

				// Execute the JavaScript callback and pass the parameter to it, which is a CefV8ValueList
				callback->ExecuteFunction(NULL, arguments);
				return true;
			}
			return false;
		}

	private:
		// aes encryption algorithm key
		QString _aes_secret_key = "a0123456789";

		/*
		* AES Algorithm string encryption
		*/
		QString encrypt(QString originText) {

			QAESEncryption encryption(QAESEncryption::AES_128, QAESEncryption::ECB, QAESEncryption::ZERO);
			QByteArray hashKey = QCryptographicHash::hash(_aes_secret_key.toUtf8(), QCryptographicHash::Md5);
			QByteArray encodedText = encryption.encode(originText.toUtf8(), hashKey);
			QString str_encode_text = QString::fromLatin1(encodedText.toBase64());
			qDebug() << "encrypt:" << originText << "    result:" << str_encode_text;
			return str_encode_text;
		}

		/*
		* AES Algorithm string decryption
		*/
		QString decrypt(QString encryptText) {
			QAESEncryption encryption(QAESEncryption::AES_128, QAESEncryption::ECB, QAESEncryption::ZERO);
			QByteArray hashKey = QCryptographicHash::hash(_aes_secret_key.toUtf8(), QCryptographicHash::Md5);
			QByteArray decodedText = encryption.decode(QByteArray::fromBase64(encryptText.toLatin1()), hashKey);
			qDebug() << "decrypt:" << encryptText << "    result:" << QString::fromUtf8(decodedText);
			return QString::fromUtf8(decodedText);
		}
		IMPLEMENT_REFCOUNTING(AppNativeV8Handler);
	};

The code is relatively long. In fact, there is only one method here. native c + + code is executed here, and JavaScript function names and other information are passed through parameters. In this method, we judge what code should be executed by judging the name of the function. Common JavaScript types have corresponding types in CEF. For details, see Reference documents

bool Execute(const CefString& name, //JavaScript function name
			CefRefPtr<CefV8Value> object,     //JavaScript function holder
			const CefV8ValueList& arguments,//JavaScript parameters
			CefRefPtr<CefV8Value>& retval,  // JavaScript return value
			CefString& exception) override

1.3. 3 implementation of onwebkitinitialized function

Implement the OnWebKitInitialized function in the qyapprender class:

// QyAppRenderer.cpp
// ... ellipsis
void QyAppRenderer::OnWebKitInitialized() {
	//1. Get the JavaScript code to be extended from the resource file
	QFile jsFile(":/extention_js.js");
	jsFile.open(QIODevice::ReadOnly);
	QByteArray jsFileData = jsFile.readAll();
	//2. JavaScript string
	QString jsCode(jsFileData);

	// 3. Register app extension module
	// When calling a function in JavaScript, you will look in the CefV8Handler list registered through CefRegisterExtension
	// Find the CefV8HandlerImpl corresponding to "v8/app", and call its Execute method
	CefRefPtr<CefV8Handler> v8Handler = new AppNativeV8Handler();
	CefRegisterExtension("v8/app", jsCode.toStdString(), v8Handler);
}
// ... ellipsis

1.3. 4 use in web pages

  • Enter the string in the first text box and click "Encrypt", and the result will be displayed in the second text box
  • Click "decrypt" and the decryption result will be displayed below
  • Click the "test app.sayHello" button, and the c + + code will call back the callback function passed in by sayHello
<!-- index.html  -->
<div>
        <input id="msg" type="text" /> <button id="btnEncrypt">encryption</button> <br />
        Encryption result:
        <input id="msgEncryptResult" type="text" />
        <button id="btnDncrypt">decrypt</button><br />
        <button id="btnTestSayHello">test app.sayHello</button><br />
</div>
<div id="dncryptInfo">

</div>
<script src="js/jquery-3.6.0.min.js"></script>
<script src="js/index.js"></script>
// index.js
console.log(app);
window.onload = () => {
    $("#btnEncrypt").click(() => {
        // encryption
        var result = app.encrypt($("#msg").val());
        $("#msgEncryptResult").val(result);
    });

    $("#btnDncrypt").click(() => {
        // decrypt
        var result = app.decrypt($("#msgEncryptResult").val());
        $("#dncryptInfo").html(" decryption result: "+ result");
    });
    $("#btnTestSayHello").click(() => {
		// c + + callback function, data is the time object passed in by c + +
        app.sayHello((data) => {
            console.log(data);
            console.log("Hello" + data);
        });
    });


}

Start the application and press F12 to open the developer tool:

You can see that the "app" object is successfully registered. The app object contains three functions: decrypt, encrypt and sayhello

Click "test app.sayHello", and the console outputs:

1.3. 5 extended summary

  • The rendering process CefApp implements the OnWebKitInitialized function in the CefRenderProcessHandler interface
  • Define the JavaScript code text in the OnWebKitInitialized function, and declare the native method in the JavaScript code text.
  • The C + + code for the specific execution of the native method should be completed by overriding the Execute method in the CefV8Handler class.
  • Associate JavaScript code with CefV8Handler through the function CefRegisterExtension.

The values of data types corresponding to JavaScript can be created through CefV8Value:: CreateXXX. The types of values of various data types in JavaScript are CefV8Value

In CefV8Value

  • CefRefPtr<CefV8Value> ExecuteFunction(CefRefPtr<CefV8Value> object,const CefV8ValueList& arguments)
  • virtual CefRefPtr<CefV8Value> ExecuteFunctionWithContext(CefRefPtr<CefV8Context> context,CefRefPtr<CefV8Value> object,const CefV8ValueList& arguments)

These two methods can execute JavaScript code,

2. Window binding

Window binding can also realize the mutual call between JavaScript and C + + code, and its idea is the same as that of extension.

This section implements binding a read-only global object window for the window object in JavaScript Appenv, which contains:

  • Cpunname: CPU name
  • cpuSn:cpu serial number
  • encrypt: encryption method (AES encryption amplification above)
  • decrypt: decryption method (the above AES decryption method)
  • hddSn: hard disk serial number
  • ipAddr: native IP address
  • macAddr: mac address of the local network card
  • Memory: native memory

Note: windows 10 operating system is collected here

When the browser JavaScript context is created, Dom has been parsed. In the OnContextCreated function of CefRenderProcessHandler interface, you can bind various JavaScript objects (strings, objects, functions, etc.) to JavaScript window objects.

2.1 OnContextCreated function

void QyAppRenderer::OnContextCreated(CefRefPtr<CefBrowser> browser,
	CefRefPtr<CefFrame> frame,
	CefRefPtr<CefV8Context> context) {
	...
}
  • Browser: current browser object
  • Frame: the frame object of the current window
  • Context: Javascript context

2.2 method of obtaining hardware information

The following code is used to obtain the hardware information. The hardware information is saved in the AppEnv structure. Here, the system hardware information is obtained by calling the wmic command in windows

// QyAppRenderer.cpp
namespace {
    // Get cpu name
	const QString CPU_NAME_CMD = "wmic cpu get name";
	// Query cpu serial number
	const QString CPU_ID_CMD = "wmic cpu get processorid";
	// View hard disk serial number
	const QString DISK_NUM_CMD = "wmic diskdrive where index=0 get serialnumber";
	// Query network card and IP address
	const QString IP_MAC_ADDRESS_CMD = "wmic PATH Win32_NetworkAdapterConfiguration WHERE \"IPEnabled = True and not MACAddress like '00:%'\" get Ipaddress,MACAddress";
	// Memory size
	const QString MEM_SIZE_CMD = "wmic ComputerSystem get TotalPhysicalMemory";

	//IP address regular expression
	QString IP_REG_PATTERN = "((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)";
	//MAC address regular expression
	QString MAC_REG_PATTERN = "([0-9a-fA-F]{2})(([/\\s:][0-9a-fA-F]{2}){5})";
    
    //Save data to structure
    typedef struct 
	{
		QString cpuSn;			//cpu serial number
		QString cpuName;	// cpu name
		QString ipAddr;			//IP address
		QString macAddr;		//Network card mac address
		QString hddSn;			// Hard disk serial number
		qint64 memory;				//Memory size in bytes
	} AppEnv;
    // Execute the command to get the returned result
    QString getWMIC(const QString& cmd)
	{
		QProcess p;
		p.start(cmd);
		p.waitForFinished();
		QString result = QString::fromLocal8Bit(p.readAllStandardOutput());
		QStringList list = cmd.split(" ");
		result = result.remove(list.last(), Qt::CaseInsensitive);
		result = result.replace("\r", "");
		result = result.replace("\n", "");
		result = result.simplified();
		return result;
	}
    
    // Get application running environment
	static AppEnv getAppEnv() {
		AppEnv appEnv;
		appEnv.cpuName = getWMIC(CPU_NAME_CMD); // CPU name
		appEnv.cpuSn = getWMIC(CPU_ID_CMD); // CPU serial number

		QString	ipMac = getWMIC(IP_MAC_ADDRESS_CMD); //IP address and mac address
		QRegExp ipRx(IP_REG_PATTERN); //IP regularization
		if (ipMac.indexOf(ipRx) >= 0) {
			appEnv.ipAddr = ipRx.cap(0);
		}

		QRegExp macRx(MAC_REG_PATTERN);//MAC regularity
		if (ipMac.indexOf(macRx) >= 0) {
			appEnv.macAddr = macRx.cap(0);
		}
		appEnv.hddSn = getWMIC(DISK_NUM_CMD); //Hard disk

		appEnv.memory = getWMIC(MEM_SIZE_CMD).toLongLong();

		return appEnv;
	}
    
    }

2.3 implementation of oncontextcreated function

In this function, it is mainly to create CefV8Value corresponding to various JavaScript objects. If a JavaScript function is created, CefV8Value::CreateFunction is required to create it. CefV8Handler is required to create the function. CefV8Handler is used to create the C + + code corresponding to the native JavaScript function.

If C + + wants to call JavaScript code, it only needs to call ExecuteFunction or ExecuteFunctionWithContext with CefV8Value

void QyAppRenderer::OnContextCreated(CefRefPtr<CefBrowser> browser,
	CefRefPtr<CefFrame> frame,
	CefRefPtr<CefV8Context> context) {
	// Get the hardware information of app running environment
	AppEnv appEnv = ::getAppEnv();

	 Retrieve the context's window object.
	CefRefPtr<CefV8Value> window = context->GetGlobal();

	//Create a JavaScript object with all read-only properties
	CefRefPtr<CefV8Value> appEnvObject = CefV8Value::CreateObject(NULL, NULL);
    // Setting property values for JavaScript objects
	appEnvObject->SetValue("cpuSn", CefV8Value::CreateString(appEnv.cpuSn.toStdString()), V8_PROPERTY_ATTRIBUTE_READONLY);
	appEnvObject->SetValue("cpuName", CefV8Value::CreateString(appEnv.cpuName.toStdString()), V8_PROPERTY_ATTRIBUTE_READONLY);
	appEnvObject->SetValue("ipAddr", CefV8Value::CreateString(appEnv.ipAddr.toStdString()), V8_PROPERTY_ATTRIBUTE_READONLY);
	appEnvObject->SetValue("macAddr", CefV8Value::CreateString(appEnv.macAddr.toStdString()), V8_PROPERTY_ATTRIBUTE_READONLY);
	appEnvObject->SetValue("hddSn", CefV8Value::CreateString(appEnv.hddSn.toStdString()), V8_PROPERTY_ATTRIBUTE_READONLY);
	appEnvObject->SetValue("memory", CefV8Value::CreateString(QString::number(appEnv.memory).toStdString()), V8_PROPERTY_ATTRIBUTE_READONLY);
	// JavaScript functions need to be handled by CefV8Handler. The AppNativeV8Handler defined earlier is used here
	CefRefPtr<CefV8Handler> handler = new AppNativeV8Handler();
	CefRefPtr<CefV8Value> funcEncrypt = CefV8Value::CreateFunction("encrypt", handler);
	CefRefPtr<CefV8Value> funcDecrypt = CefV8Value::CreateFunction("decrypt", handler);
	appEnvObject->SetValue("encrypt", funcEncrypt, V8_PROPERTY_ATTRIBUTE_NONE);
	appEnvObject->SetValue("decrypt", funcDecrypt, V8_PROPERTY_ATTRIBUTE_NONE);

	//It is bound to the window object and is also a read-only attribute
	window->SetValue("appEnv", appEnvObject, V8_PROPERTY_ATTRIBUTE_READONLY);
}

2.4 test whether the binding is successful

In index JS, print out window Appenv to view

// index.js
if (window.appEnv) {
    console.log(window.appEnv);
}

Start the application and open F12:

The next chapter will try how to implement asynchronous calls between JavaScript and C + +.

Keywords: C++ Javascript Qt chrome cef

Added by dsartain on Wed, 22 Dec 2021 13:31:14 +0200