凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

QML の WebView 内の JavaScript と外の JavaScript でやり取りをする

はじめに

QML の WebView 内をゴニョゴニョしたいなと思って調べると、evaluateJavaScript すれば出来るよ!と書いてあったのですが、どうやら Qt Quick 2.0 の QtWebKit 3.0 からは API が変わったようです。

そこでもう少し調べてみると experimental で evaluateJavaScript が使えることが分かりました。

そこで色々試してみた内容をメモします。

使い方

WebView の使い方は以下を参照:

例えば背景を白くするとかだったらこんな感じです。

import QtQuick 2.0
import QtWebKit 3.0
import QtWebKit.experimental 1.0

Rectangle {
    width: 960
    height: 540
    WebView {
        url: 'https://twitter.com/'
        anchors.fill: parent
        onLoadProgressChanged: {
            progressBar.width = parent.width * loadProgress / 100;
        }
        onLoadingChanged: {
            if (loadRequest.status == WebView.LoadSucceededStatus) {
                var js = 'document.body.style.backgroundColor = "#fff";';
                experimental.evaluateJavaScript(js);
            }
        }
    }
    Rectangle {
        id: progressBar
        anchors.left: parent.left
        anchors.top: parent.top
        width: 0
        height: 5
        color: (width == parent.width) ? 'transparent' : '#aa000000'
    }
}

外と中の JavaScript のやり取りの仕方

2種類あります。

  1. evaluateJavaScript の第2引数に指定するコールバックで値を受け取る
  2. navigator.qt.postMessage で外側の onMessageReceived で受け取る
◆ evaluateJavaScript の第2引数に指定するコールバックで値を受け取る

eval で最後に評価した値が第2引数のコールバックの引数へ渡されます。以下のようにすると Tweet の中身を取得出来ます。

import QtQuick 2.0
import QtWebKit 3.0
import QtWebKit.experimental 1.0

Rectangle {
    width: 960
    height: 540
    WebView {
        url: 'https://twitter.com/'
        anchors.fill: parent
        onLoadProgressChanged: {
            progressBar.width = parent.width * loadProgress / 100;
        }
        onLoadingChanged: {
            if (loadRequest.status == WebView.LoadSucceededStatus) {
                var js = '(function() {' +
                             'var i, result = "";' +
                             'var tweets = document.getElementsByClassName("tweet-text");' +
                             'return Array.prototype.map.call(tweets, function(t) {return t.innerText;});' +
                         '})()'
                experimental.evaluateJavaScript(js, function(tweets) {
                    for (var i in tweets) console.log(i, tweets[i]);
                });
            }
        }
    }
    ...(略)
}

result には Array でなくて Object が入っているのに注意。ちょっと試してみましたが、中から外へは Object は単一のキーを持っているもの(e.g. {hoge: 'piyo'}) しか無理?なようです。

◆ navigator.qt.postMessage で外側の onMessageReceived で受け取る

長いですが experimental.preferences.navigatorQtObjectEnabled を true にする必要があります。また postMessage でやり取りできるデータは文字列のみのようです。

import QtQuick 2.0
import QtWebKit 3.0
import QtWebKit.experimental 1.0

Rectangle {
    width: 960
    height: 540
    WebView {
        url: 'https://twitter.com/'
        anchors.fill: parent
        onLoadProgressChanged: {
            progressBar.width = parent.width * loadProgress / 100;
        }
        experimental.preferences.navigatorQtObjectEnabled: true
        onLoadingChanged: {
            if (loadRequest.status == WebView.LoadSucceededStatus) {
                var js = 'navigator.qt.onmessage = function(ev) {' +
                    'navigator.qt.postMessage(ev.data);' +
                '}';
                experimental.evaluateJavaScript(js);
            }
            experimental.postMessage('fugafuga');
        }
        experimental.onMessageReceived: {
            console.log(message.data);
        }
    }
    ...(略)
}

これで QML 側から experimental.postMessage で 'fugafuga' を送って、それが WebView 内の navigator.qt.onmessage を経由して navigator.qt.postMessage され、QML 側の experimental.onMessageReceieved に送られてきました。

おまけ

experimental を使わなくても WebView.data[1] に evaluateJavaScript が入ってました。

var js = '(function() {' +
             'var i, result = "";' +
             'var tweets = document.getElementsByClassName("tweet-text");' +
             'return Array.prototype.map.call(tweets, function(t) {return t.innerText;});' +
         '})();';
data[1].evaluateJavaScript(js, function(tweets) {
    for (var i in tweets) console.log(i, tweets[i]);
});

おわりに

QtWebKit 3.0 用に Qt Creator の補完が働いてくれなくてイライラしてます。