#include "config.h"
#include "WPEQtView.h"
#include "WPEQtViewLoadRequest.h"
#include "WPEQtViewLoadRequestPrivate.h"
#include <QGuiApplication>
#include <QQuickWindow>
#include <QSGSimpleTextureNode>
#include <QScreen>
#include <QtPlatformHeaders/QEGLNativeContext>
#include <qpa/qplatformnativeinterface.h>
#include <wtf/glib/GUniquePtr.h>
WPEQtView::WPEQtView(QQuickItem* parent)
: QQuickItem(parent)
{
connect(this, &QQuickItem::windowChanged, this, &WPEQtView::configureWindow);
setFlag(ItemHasContents, true);
setAcceptedMouseButtons(Qt::AllButtons);
setAcceptHoverEvents(true);
setAcceptTouchEvents(true);
}
WPEQtView::~WPEQtView()
{
g_signal_handlers_disconnect_by_func(m_webView.get(), reinterpret_cast<gpointer>(notifyUrlChangedCallback), this);
g_signal_handlers_disconnect_by_func(m_webView.get(), reinterpret_cast<gpointer>(notifyTitleChangedCallback), this);
g_signal_handlers_disconnect_by_func(m_webView.get(), reinterpret_cast<gpointer>(notifyLoadChangedCallback), this);
g_signal_handlers_disconnect_by_func(m_webView.get(), reinterpret_cast<gpointer>(notifyLoadFailedCallback), this);
g_signal_handlers_disconnect_by_func(m_webView.get(), reinterpret_cast<gpointer>(notifyLoadProgressCallback), this);
}
void WPEQtView::geometryChanged(const QRectF& newGeometry, const QRectF&)
{
m_size = newGeometry.size();
if (m_backend)
m_backend->resize(newGeometry.size());
}
void WPEQtView::configureWindow()
{
auto* win = window();
if (!win)
return;
win->setSurfaceType(QWindow::OpenGLSurface);
connect(win, &QQuickWindow::sceneGraphInitialized, this, &WPEQtView::createWebView);
}
void WPEQtView::createWebView()
{
if (m_backend)
return;
auto display = static_cast<EGLDisplay>(QGuiApplication::platformNativeInterface()->nativeResourceForIntegration("egldisplay"));
auto* context = window()->openglContext();
std::unique_ptr<WPEQtViewBackend> backend = WPEQtViewBackend::create(m_size, context, display, QPointer<WPEQtView>(this));
RELEASE_ASSERT_WITH_MESSAGE(backend, "EGL initialization failed");
if (!backend)
return;
m_backend = backend.get();
auto settings = adoptGRef(webkit_settings_new_with_settings("enable-developer-extras", TRUE,
"enable-webgl", TRUE, "enable-mediasource", TRUE, nullptr));
m_webView = adoptGRef(WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW,
"backend", webkit_web_view_backend_new(m_backend->backend(), [](gpointer data) {
delete static_cast<WPEQtViewBackend*>(data);
}, backend.release()),
"settings", settings.get(), nullptr)));
g_signal_connect_swapped(m_webView.get(), "notify::uri", G_CALLBACK(notifyUrlChangedCallback), this);
g_signal_connect_swapped(m_webView.get(), "notify::title", G_CALLBACK(notifyTitleChangedCallback), this);
g_signal_connect_swapped(m_webView.get(), "notify::estimated-load-progress", G_CALLBACK(notifyLoadProgressCallback), this);
g_signal_connect(m_webView.get(), "load-changed", G_CALLBACK(notifyLoadChangedCallback), this);
g_signal_connect(m_webView.get(), "load-failed", G_CALLBACK(notifyLoadFailedCallback), this);
if (!m_url.isEmpty())
webkit_web_view_load_uri(m_webView.get(), m_url.toString().toUtf8().constData());
else if (!m_html.isEmpty())
webkit_web_view_load_html(m_webView.get(), m_html.toUtf8().constData(), m_baseUrl.toString().toUtf8().constData());
Q_EMIT webViewCreated();
}
void WPEQtView::notifyUrlChangedCallback(WPEQtView* view)
{
Q_EMIT view->urlChanged();
}
void WPEQtView::notifyTitleChangedCallback(WPEQtView* view)
{
Q_EMIT view->titleChanged();
}
void WPEQtView::notifyLoadProgressCallback(WPEQtView* view)
{
Q_EMIT view->loadProgressChanged();
}
void WPEQtView::notifyLoadChangedCallback(WebKitWebView*, WebKitLoadEvent event, WPEQtView* view)
{
bool statusSet = false;
WPEQtView::LoadStatus loadStatus;
switch (event) {
case WEBKIT_LOAD_STARTED:
loadStatus = WPEQtView::LoadStatus::LoadStartedStatus;
statusSet = true;
break;
case WEBKIT_LOAD_FINISHED:
loadStatus = WPEQtView::LoadStatus::LoadSucceededStatus;
statusSet = !view->errorOccured();
view->setErrorOccured(false);
break;
default:
break;
}
if (statusSet) {
WPEQtViewLoadRequestPrivate loadRequestPrivate(view->url(), loadStatus, "");
std::unique_ptr<WPEQtViewLoadRequest> loadRequest = makeUnique<WPEQtViewLoadRequest>(loadRequestPrivate);
Q_EMIT view->loadingChanged(loadRequest.get());
}
}
void WPEQtView::notifyLoadFailedCallback(WebKitWebView*, WebKitLoadEvent, const gchar* failingURI, GError* error, WPEQtView* view)
{
view->setErrorOccured(true);
WPEQtView::LoadStatus loadStatus;
if (g_error_matches(error, WEBKIT_NETWORK_ERROR, WEBKIT_NETWORK_ERROR_CANCELLED))
loadStatus = WPEQtView::LoadStatus::LoadStoppedStatus;
else
loadStatus = WPEQtView::LoadStatus::LoadFailedStatus;
WPEQtViewLoadRequestPrivate loadRequestPrivate(QUrl(QString(failingURI)), loadStatus, error->message);
std::unique_ptr<WPEQtViewLoadRequest> loadRequest = makeUnique<WPEQtViewLoadRequest>(loadRequestPrivate);
Q_EMIT view->loadingChanged(loadRequest.get());
}
QSGNode* WPEQtView::updatePaintNode(QSGNode* node, UpdatePaintNodeData*)
{
if (!m_webView || !m_backend)
return node;
auto* textureNode = static_cast<QSGSimpleTextureNode*>(node);
if (!textureNode)
textureNode = new QSGSimpleTextureNode();
GLuint textureId = m_backend->texture(window()->openglContext());
if (!textureId)
return node;
textureNode->setTexture(window()->createTextureFromId(textureId, m_size.toSize(), QQuickWindow::TextureHasAlphaChannel));
textureNode->setRect(boundingRect());
return textureNode;
}
QUrl WPEQtView::url() const
{
if (!m_webView)
return m_url;
const gchar* uri = webkit_web_view_get_uri(m_webView.get());
return uri ? QUrl(QString(uri)) : m_url;
}
void WPEQtView::setUrl(const QUrl& url)
{
if (url == m_url)
return;
m_errorOccured = false;
m_url = url;
if (m_webView)
webkit_web_view_load_uri(m_webView.get(), m_url.toString().toUtf8().constData());
}
int WPEQtView::loadProgress() const
{
if (!m_webView)
return 0;
return webkit_web_view_get_estimated_load_progress(m_webView.get()) * 100;
}
QString WPEQtView::title() const
{
if (!m_webView)
return "";
return webkit_web_view_get_title(m_webView.get());
}
bool WPEQtView::canGoBack() const
{
if (!m_webView)
return false;
return webkit_web_view_can_go_back(m_webView.get());
}
bool WPEQtView::isLoading() const
{
if (!m_webView)
return false;
return webkit_web_view_is_loading(m_webView.get());
}
bool WPEQtView::canGoForward() const
{
if (!m_webView)
return false;
return webkit_web_view_can_go_forward(m_webView.get());
}
void WPEQtView::goBack()
{
if (m_webView)
webkit_web_view_go_back(m_webView.get());
}
void WPEQtView::goForward()
{
if (m_webView)
webkit_web_view_go_forward(m_webView.get());
}
void WPEQtView::reload()
{
if (m_webView)
webkit_web_view_reload(m_webView.get());
}
void WPEQtView::stop()
{
if (m_webView)
webkit_web_view_stop_loading(m_webView.get());
}
void WPEQtView::loadHtml(const QString& html, const QUrl& baseUrl)
{
m_html = html;
m_baseUrl = baseUrl;
m_errorOccured = false;
if (m_webView)
webkit_web_view_load_html(m_webView.get(), html.toUtf8().constData(), baseUrl.toString().toUtf8().constData());
}
struct JavascriptCallbackData {
WTF_MAKE_STRUCT_FAST_ALLOCATED;
JavascriptCallbackData(QJSValue cb, QPointer<WPEQtView> obj)
: callback(cb)
, object(obj) { }
QJSValue callback;
QPointer<WPEQtView> object;
};
static void jsAsyncReadyCallback(GObject* object, GAsyncResult* result, gpointer userData)
{
GUniqueOutPtr<GError> error;
std::unique_ptr<JavascriptCallbackData> data(reinterpret_cast<JavascriptCallbackData*>(userData));
WebKitJavascriptResult* jsResult = webkit_web_view_run_javascript_finish(WEBKIT_WEB_VIEW(object), result, &error.outPtr());
if (!jsResult) {
qWarning("Error running javascript: %s", error->message);
return;
}
if (data->object.data()) {
QQmlEngine* engine = qmlEngine(data->object.data());
if (!engine) {
qWarning("No JavaScript engine, unable to handle JavaScript callback!");
webkit_javascript_result_unref(jsResult);
return;
}
QJSValueList args;
JSCValue* value = webkit_javascript_result_get_js_value(jsResult);
QVariant variant;
if (jsc_value_is_string(value)) {
GUniquePtr<gchar> strValue(jsc_value_to_string(value));
JSCContext* context = jsc_value_get_context(value);
JSCException* exception = jsc_context_get_exception(context);
if (exception) {
qWarning("Error running javascript: %s", jsc_exception_get_message(exception));
jsc_context_clear_exception(context);
} else
variant.setValue(QString(g_strdup(strValue.get())));
}
args.append(engine->toScriptValue(variant));
data->callback.call(args);
}
webkit_javascript_result_unref(jsResult);
}
void WPEQtView::runJavaScript(const QString& script, const QJSValue& callback)
{
std::unique_ptr<JavascriptCallbackData> data = makeUnique<JavascriptCallbackData>(callback, QPointer<WPEQtView>(this));
webkit_web_view_run_javascript(m_webView.get(), script.toUtf8().constData(), nullptr, jsAsyncReadyCallback, data.release());
}
void WPEQtView::mousePressEvent(QMouseEvent* event)
{
forceActiveFocus();
if (m_backend)
m_backend->dispatchMousePressEvent(event);
}
void WPEQtView::mouseReleaseEvent(QMouseEvent* event)
{
if (m_backend)
m_backend->dispatchMouseReleaseEvent(event);
}
void WPEQtView::hoverEnterEvent(QHoverEvent* event)
{
if (m_backend)
m_backend->dispatchHoverEnterEvent(event);
}
void WPEQtView::hoverLeaveEvent(QHoverEvent* event)
{
if (m_backend)
m_backend->dispatchHoverLeaveEvent(event);
}
void WPEQtView::hoverMoveEvent(QHoverEvent* event)
{
if (m_backend)
m_backend->dispatchHoverMoveEvent(event);
}
void WPEQtView::wheelEvent(QWheelEvent* event)
{
if (m_backend)
m_backend->dispatchWheelEvent(event);
}
void WPEQtView::keyPressEvent(QKeyEvent* event)
{
if (m_backend)
m_backend->dispatchKeyEvent(event, true);
}
void WPEQtView::keyReleaseEvent(QKeyEvent* event)
{
if (m_backend)
m_backend->dispatchKeyEvent(event, false);
}
void WPEQtView::touchEvent(QTouchEvent* event)
{
if (m_backend)
m_backend->dispatchTouchEvent(event);
}