Qt开发:QWebEnginePage执行自定义JavaScript

runJavaScript方法

QWebEnginePage有一个方法runJavaScript,可以执行自定义的JavaScript代码。

它的原型如下:

void QWebEnginePage::runJavaScript(const QString &scriptSource, const std::function<void (const QVariant &)> &resultCallback);

void QWebEnginePage::runJavaScript(const QString &scriptSource, quint32 worldId = 0, const std::function<void (const QVariant &)> &resultCallback = {});

其中,scriptSource是QString类型的JavaScript源代码。

worldId其实是一个枚举,但是这里使用了quint32类型,因为2以上可以自定义,分成几种情况:

  • worldId是0(MainWorld),表示在全局作用域中,与网页本身的JavaScript共享同一上下文,可以与页面进行交互;
  • worldId是1(ApplicationWorld),表示在应用作用域中,只是注入应用逻辑,不会与原网页的JavaScript互相干扰;
  • worldId是2,以及多大的自定义值,每个都是一个自定义上下文。

比如可以直接修改页面标题:

page->runJavaScript("document.title = '新标题';", QWebEngineScript::MainWorld);

或者只添加一个应用按钮:

page->runJavaScript(R"(
    const button = document.createElement('button');
    button.textContent = '自定义应用按钮';
    document.body.appendChild(button);
)", QWebEngineScript::ApplicationWorld);

resultCallback是一个回调函数,如果执行的JavaScript有返回值,会以QVariant的类型作为参数回调。

如获取当前页面大小:

page->runJavaScript (  
    "getCurrentSize();",  
    [] (const QVariant &result) {  
      if (result.isValid () && result.typeId () == QMetaType::QVariantList)  
        {  
          auto sizes = result.toList ();  
          auto width = offsets[0].toInt ();  
          auto height = offsets[1].toInt ();  
          qDebug () << "Width:" << width;  
          qDebug () << "Height:" << height;  
        }
    });

runJavaScript的异步性

需要注意的是,QWebEnginePage的runJavaScript调用,是异步执行的。

即,当我们调用了runJavaScript以后,当前的JavaScript脚本只是被执行去了,后续的代码继续执行,当JavaScript执行完成以后,回调函数里的代码再被执行。

比如如下代码,我们需要使用JavaScript获取选择的偏移量,进而进行后续的处理,就会发现每次记录begin和end的值都是-1。

int begin = -1;  
int end = -1;

page->runJavaScript (
      "getSelectionOffset(0);",
      [&begin, &end] (const QVariant &result) {
        if (result.isValid () && result.typeId () == QMetaType::QVariantList)
          {
            auto offsets = result.toList ();
            begin = offsets[0].toInt ();
            end = offsets[1].toInt ();
            qDebug () << "Begin offset:" << offsets[0].toInt ();
            qDebug () << "End offset:" << offsets[1].toInt ();
          }
      });

qDebug() << "begin: " << begin;
qDebug() << "end: " << end;

为了解决这个问题,我们可以创建一个QEventLoop,在每次执行JavaScript的回调函数里,释放这个QEventLoop。

如以上的代码可以改成:

int begin = -1;  
int end = -1;

QEventLoop loop;

page->runJavaScript (
      "getSelectionOffset(0);",
      [&begin, &end] (const QVariant &result) {
        if (result.isValid () && result.typeId () == QMetaType::QVariantList)
          {
            auto offsets = result.toList ();
            begin = offsets[0].toInt ();
            end = offsets[1].toInt ();
            qDebug () << "Begin offset:" << offsets[0].toInt ();
            qDebug () << "End offset:" << offsets[1].toInt ();
            // loop调用quit(),后面的loop.exec()将返回。
            loop.quit();
          }
      });

// loop将在这里等待,直到loop.quit()在回调里被执行。
loop.exec();

qDebug() << "begin: " << begin;
qDebug() << "end: " << end;

runJavaScript的上下文隔离

在QWebEnginePage中,每次调用runJavaScript都是在一个独立的上下文中执行的,函数定义不会持久化到全局的作用域中。这意味着在一次runJavaScript调用中定义的函数,在后续的runJavaScript调用中无法直接访问。

有几种方法可以解决这个问题:

一、将函数定义到window对象上(全局作用域):

比如,第一次调用时,如下实现:

后续调用时,使用全局window对象:

page.runJavaScript("window.myFunction();");

二、通过注入自定义JavaScript文件:

创建QWebEngineScript对象:

auto script = QWebEngineScript{};
script.setName("customFunctions");   
script.setSourceCode("
        function myFunction() {
            console.log('Hello');
        }
");
script.setInjectionPoint(QWebEngineScript::DocumentCreation);
script.setWorldId(QWebEngineScript::MainWorld);
script.setRunsOnSubFrames(true);

把QWebEngineScript插入到QWebEngineScriptCollection中去:

auto page = webview.page();
page->scripts().insert(script);

后续调用时:

page->runJavaScript("myFunction();");

需要注意的是,如果使用第1种方法(window对象),要确保在页面完全加载后再定义函数,否则可能会因为页面重载而丢失函数定义。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值