init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "webview/platform/linux/webview_linux.h"
|
||||
|
||||
#include "base/random.h"
|
||||
#include "webview/platform/linux/webview_linux_webkitgtk.h"
|
||||
|
||||
namespace Webview {
|
||||
|
||||
Available Availability() {
|
||||
return WebKitGTK::Availability();
|
||||
}
|
||||
|
||||
bool SupportsEmbedAfterCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SeparateStorageIdSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<Interface> CreateInstance(Config config) {
|
||||
return WebKitGTK::CreateInstance(std::move(config));
|
||||
}
|
||||
|
||||
std::string GenerateStorageToken() {
|
||||
constexpr auto kSize = 16;
|
||||
auto result = std::string(kSize, ' ');
|
||||
base::RandomFill(result.data(), result.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
void ClearStorageDataByToken(const std::string &token) {
|
||||
}
|
||||
|
||||
} // namespace Webview
|
||||
@@ -0,0 +1,9 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "webview/webview_interface.h"
|
||||
@@ -0,0 +1,306 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "webview/platform/linux/webview_linux_compositor.h"
|
||||
|
||||
#ifdef DESKTOP_APP_WEBVIEW_WAYLAND_COMPOSITOR
|
||||
#include "base/flat_map.h"
|
||||
#include "base/unique_qptr.h"
|
||||
#include "base/qt_signal_producer.h"
|
||||
#include "base/event_filter.h"
|
||||
|
||||
#include <QtQuickWidgets/QQuickWidget>
|
||||
#include <QtWaylandCompositor/QWaylandXdgSurface>
|
||||
#include <QtWaylandCompositor/QWaylandXdgOutputV1>
|
||||
#include <QtWaylandCompositor/QWaylandQuickOutput>
|
||||
#include <QtWaylandCompositor/QWaylandQuickShellSurfaceItem>
|
||||
|
||||
namespace Webview {
|
||||
|
||||
struct Compositor::Private {
|
||||
Private(Compositor *parent)
|
||||
: shell(parent)
|
||||
, xdgOutput(parent) {
|
||||
}
|
||||
|
||||
QPointer<QQuickWidget> widget;
|
||||
base::unique_qptr<Output> output;
|
||||
QWaylandXdgShell shell;
|
||||
QWaylandXdgOutputManagerV1 xdgOutput;
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
|
||||
class Compositor::Chrome : public QWaylandQuickShellSurfaceItem {
|
||||
public:
|
||||
Chrome(
|
||||
Output *output,
|
||||
QQuickWindow *window,
|
||||
QWaylandXdgSurface *xdgSurface,
|
||||
bool windowFollowsSize);
|
||||
|
||||
rpl::producer<> surfaceCompleted() const {
|
||||
return _completed.value()
|
||||
| rpl::filter(rpl::mappers::_1)
|
||||
| rpl::to_empty;
|
||||
}
|
||||
|
||||
private:
|
||||
QQuickItem _moveItem;
|
||||
rpl::variable<bool> _completed = false;
|
||||
rpl::lifetime _lifetime;
|
||||
};
|
||||
|
||||
class Compositor::Output : public QWaylandQuickOutput {
|
||||
public:
|
||||
Output(Compositor *compositor, QObject *parent = nullptr)
|
||||
: _xdg(this, &compositor->_private->xdgOutput) {
|
||||
const auto xdgSurface = qobject_cast<QWaylandXdgSurface*>(parent);
|
||||
const auto window = qobject_cast<QQuickWindow*>(parent);
|
||||
setParent(parent);
|
||||
setCompositor(compositor);
|
||||
setWindow(window ? window : &_ownedWindow.emplace());
|
||||
setScaleFactor(this->window()->devicePixelRatio());
|
||||
setSizeFollowsWindow(true);
|
||||
this->window()->setProperty("output", QVariant::fromValue(this));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
||||
base::install_event_filter(this, this->window(), [=](
|
||||
not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::DevicePixelRatioChange) {
|
||||
setScaleFactor(this->window()->devicePixelRatio());
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
#endif // Qt >= 6.6.0
|
||||
rpl::single(rpl::empty) | rpl::then(
|
||||
rpl::merge(
|
||||
base::qt_signal_producer(
|
||||
this,
|
||||
&QWaylandOutput::geometryChanged
|
||||
),
|
||||
base::qt_signal_producer(
|
||||
this,
|
||||
&QWaylandOutput::scaleFactorChanged
|
||||
)
|
||||
)
|
||||
) | rpl::map([=] {
|
||||
return std::make_tuple(geometry(), scaleFactor());
|
||||
}) | rpl::on_next([=](QRect geometry, int scaleFactor) {
|
||||
_xdg.setLogicalPosition(geometry.topLeft() / scaleFactor);
|
||||
_xdg.setLogicalSize(geometry.size() / scaleFactor);
|
||||
}, _lifetime);
|
||||
setXdgSurface(xdgSurface);
|
||||
}
|
||||
|
||||
QQuickWindow *window() const {
|
||||
return static_cast<QQuickWindow*>(QWaylandQuickOutput::window());
|
||||
}
|
||||
|
||||
Chrome *chrome() const {
|
||||
return _chrome;
|
||||
}
|
||||
|
||||
void setXdgSurface(QWaylandXdgSurface *xdgSurface) {
|
||||
if (xdgSurface) {
|
||||
_chrome.emplace(this, window(), xdgSurface, bool(_ownedWindow));
|
||||
} else {
|
||||
_chrome.reset();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
QWaylandXdgOutputV1 _xdg;
|
||||
std::optional<QQuickWindow> _ownedWindow;
|
||||
base::unique_qptr<Chrome> _chrome;
|
||||
rpl::lifetime _lifetime;
|
||||
};
|
||||
|
||||
Compositor::Chrome::Chrome(
|
||||
Output *output,
|
||||
QQuickWindow *window,
|
||||
QWaylandXdgSurface *xdgSurface,
|
||||
bool windowFollowsSize)
|
||||
: QWaylandQuickShellSurfaceItem(window->contentItem()) {
|
||||
base::qt_signal_producer(
|
||||
xdgSurface,
|
||||
&QObject::destroyed
|
||||
) | rpl::on_next([=] {
|
||||
delete this;
|
||||
}, _lifetime);
|
||||
|
||||
rpl::single(rpl::empty) | rpl::then(
|
||||
base::qt_signal_producer(
|
||||
view(),
|
||||
&QWaylandView::surfaceChanged
|
||||
)
|
||||
) | rpl::on_next([=] {
|
||||
setOutput(output);
|
||||
}, _lifetime);
|
||||
|
||||
setShellSurface(xdgSurface);
|
||||
setAutoCreatePopupItems(false);
|
||||
setMoveItem(&_moveItem);
|
||||
_moveItem.setEnabled(false);
|
||||
xdgSurface->setProperty("window", QVariant::fromValue(window));
|
||||
|
||||
base::install_event_filter(this, window, [=](not_null<QEvent*> e) {
|
||||
if (e->type() != QEvent::Close) {
|
||||
return base::EventFilterResult::Continue;
|
||||
}
|
||||
e->ignore();
|
||||
if (const auto toplevel = xdgSurface->toplevel()) {
|
||||
toplevel->sendClose();
|
||||
} else if (const auto popup = xdgSurface->popup()) {
|
||||
popup->sendPopupDone();
|
||||
}
|
||||
return base::EventFilterResult::Cancel;
|
||||
});
|
||||
|
||||
rpl::single(rpl::empty) | rpl::then(
|
||||
rpl::merge(
|
||||
base::qt_signal_producer(
|
||||
window,
|
||||
&QWindow::widthChanged
|
||||
),
|
||||
base::qt_signal_producer(
|
||||
window,
|
||||
&QWindow::heightChanged
|
||||
)
|
||||
) | rpl::to_empty
|
||||
) | rpl::map([=] {
|
||||
return window->size();
|
||||
}) | rpl::distinct_until_changed(
|
||||
) | rpl::filter([=](const QSize &size) {
|
||||
return !size.isEmpty();
|
||||
}) | rpl::on_next([=](const QSize &size) {
|
||||
if (const auto toplevel = xdgSurface->toplevel()) {
|
||||
toplevel->sendFullscreen(size);
|
||||
}
|
||||
}, _lifetime);
|
||||
|
||||
rpl::single(rpl::empty) | rpl::then(
|
||||
rpl::merge(
|
||||
base::qt_signal_producer(
|
||||
xdgSurface->surface(),
|
||||
&QWaylandSurface::destinationSizeChanged
|
||||
),
|
||||
base::qt_signal_producer(
|
||||
xdgSurface,
|
||||
&QWaylandXdgSurface::windowGeometryChanged
|
||||
)
|
||||
)
|
||||
) | rpl::map([=] {
|
||||
return xdgSurface->windowGeometry().isValid()
|
||||
? xdgSurface->windowGeometry()
|
||||
: QRect(QPoint(), xdgSurface->surface()->destinationSize());
|
||||
}) | rpl::distinct_until_changed(
|
||||
) | rpl::filter([=](const QRect &geometry) {
|
||||
return geometry.isValid();
|
||||
}) | rpl::on_next([=](const QRect &geometry) {
|
||||
setX(-geometry.x());
|
||||
setY(-geometry.y());
|
||||
|
||||
if (windowFollowsSize) {
|
||||
if (xdgSurface->popup()) {
|
||||
window->setMinimumSize(geometry.size());
|
||||
window->setMaximumSize(geometry.size());
|
||||
} else {
|
||||
window->resize(geometry.size());
|
||||
}
|
||||
}
|
||||
|
||||
_completed = true;
|
||||
}, _lifetime);
|
||||
|
||||
if (const auto toplevel = xdgSurface->toplevel()) {
|
||||
rpl::single(rpl::empty) | rpl::then(
|
||||
base::qt_signal_producer(
|
||||
toplevel,
|
||||
&QWaylandXdgToplevel::titleChanged
|
||||
)
|
||||
) | rpl::map([=] {
|
||||
return toplevel->title();
|
||||
}) | rpl::on_next([=](const QString &title) {
|
||||
window->setTitle(title);
|
||||
}, _lifetime);
|
||||
|
||||
rpl::single(rpl::empty) | rpl::then(
|
||||
base::qt_signal_producer(
|
||||
toplevel,
|
||||
&QWaylandXdgToplevel::fullscreenChanged
|
||||
)
|
||||
) | rpl::map([=] {
|
||||
return toplevel->fullscreen();
|
||||
}) | rpl::on_next([=](bool fullscreen) {
|
||||
if (!fullscreen) {
|
||||
toplevel->sendFullscreen(window->size());
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
}
|
||||
|
||||
Compositor::Compositor(const QByteArray &socketName)
|
||||
: _private(std::make_unique<Private>(this)) {
|
||||
connect(&_private->shell, &QWaylandXdgShell::toplevelCreated, [=](
|
||||
QWaylandXdgToplevel *toplevel,
|
||||
QWaylandXdgSurface *xdgSurface) {
|
||||
if (!_private->output || _private->output->chrome()) {
|
||||
const auto output = new Output(this, xdgSurface);
|
||||
|
||||
output->chrome()->surfaceCompleted() | rpl::on_next([=] {
|
||||
output->window()->show();
|
||||
}, _private->lifetime);
|
||||
} else {
|
||||
_private->output->setXdgSurface(xdgSurface);
|
||||
}
|
||||
});
|
||||
|
||||
connect(&_private->shell, &QWaylandXdgShell::popupCreated, [=](
|
||||
QWaylandXdgPopup *popup,
|
||||
QWaylandXdgSurface *xdgSurface) {
|
||||
const auto widget = _private->widget;
|
||||
const auto parent = (*static_cast<QQuickWindow * const *>(
|
||||
popup->parentXdgSurface()->property("window").constData()
|
||||
));
|
||||
const auto output = (*static_cast<Output * const *>(
|
||||
parent->property("output").constData()
|
||||
));
|
||||
const auto window = new QQuickWindow;
|
||||
static_cast<QObject*>(window)->setParent(xdgSurface);
|
||||
window->setProperty("output", QVariant::fromValue(output));
|
||||
const auto chrome = new Chrome(output, window, xdgSurface, true);
|
||||
|
||||
chrome->surfaceCompleted() | rpl::on_next([=] {
|
||||
if (widget && parent == widget->quickWindow()) {
|
||||
window->setTransientParent(widget->window()->windowHandle());
|
||||
window->setPosition(
|
||||
popup->unconstrainedPosition()
|
||||
+ widget->mapToGlobal(QPoint()));
|
||||
} else {
|
||||
window->setTransientParent(parent);
|
||||
window->setPosition(
|
||||
popup->unconstrainedPosition() + parent->position());
|
||||
}
|
||||
window->setFlag(Qt::Popup);
|
||||
window->setColor(Qt::transparent);
|
||||
window->show();
|
||||
}, _private->lifetime);
|
||||
});
|
||||
|
||||
setSocketName(socketName);
|
||||
create();
|
||||
}
|
||||
|
||||
void Compositor::setWidget(QQuickWidget *widget) {
|
||||
_private->widget = widget;
|
||||
if (widget) {
|
||||
_private->output.emplace(this, widget->quickWindow());
|
||||
} else {
|
||||
_private->output.reset();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Webview
|
||||
#endif // DESKTOP_APP_WEBVIEW_WAYLAND_COMPOSITOR
|
||||
@@ -0,0 +1,51 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#if defined QT_QUICKWIDGETS_LIB && defined QT_WAYLANDCOMPOSITOR_LIB
|
||||
#include <QtWaylandCompositor/qtwaylandcompositor-config.h>
|
||||
|
||||
#if QT_CONFIG(wayland_compositor_quick)
|
||||
#include <QtWaylandCompositor/QWaylandQuickCompositor>
|
||||
|
||||
#define DESKTOP_APP_WEBVIEW_WAYLAND_COMPOSITOR
|
||||
|
||||
class QQuickWidget;
|
||||
|
||||
namespace Webview {
|
||||
|
||||
class Compositor : public QWaylandQuickCompositor {
|
||||
public:
|
||||
Compositor(const QByteArray &socketName = {});
|
||||
|
||||
void setWidget(QQuickWidget *widget);
|
||||
|
||||
private:
|
||||
class Output;
|
||||
class Chrome;
|
||||
|
||||
struct Private;
|
||||
const std::unique_ptr<Private> _private;
|
||||
};
|
||||
|
||||
} // namespace Webview
|
||||
#endif // QT_CONFIG(wayland_compositor_quick)
|
||||
#endif // QT_QUICKWIDGETS_LIB && QT_WAYLANDCOMPOSITOR_LIB
|
||||
|
||||
#ifndef DESKTOP_APP_WEBVIEW_WAYLAND_COMPOSITOR
|
||||
namespace Webview {
|
||||
|
||||
class Compositor : public QObject {
|
||||
public:
|
||||
Compositor(const QByteArray &socketName = {}) {}
|
||||
QString socketName() { return {}; }
|
||||
};
|
||||
|
||||
} // namespace Webview
|
||||
#endif // !DESKTOP_APP_WEBVIEW_WAYLAND_COMPOSITOR
|
||||
@@ -0,0 +1,189 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "webview/platform/linux/webview_linux_http_server.h"
|
||||
|
||||
#include <QtCore/QPointer>
|
||||
#include <QtNetwork/QTcpSocket>
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
#include <crl/crl.h>
|
||||
|
||||
namespace Webview {
|
||||
|
||||
struct HttpServer::Private {
|
||||
void handleRequest(QTcpSocket *socket);
|
||||
|
||||
bool processRedirect(
|
||||
QTcpSocket *socket,
|
||||
const QByteArray &id,
|
||||
const ::base::flat_map<QByteArray, QByteArray> &headers,
|
||||
const std::shared_ptr<Guard> &guard);
|
||||
|
||||
QNetworkAccessManager manager;
|
||||
QByteArray password;
|
||||
std::function<void(
|
||||
QTcpSocket *socket,
|
||||
const QByteArray &id,
|
||||
const ::base::flat_map<QByteArray, QByteArray> &headers,
|
||||
const std::shared_ptr<Guard> &guard)> handler;
|
||||
};
|
||||
|
||||
void HttpServer::Private::handleRequest(QTcpSocket *socket) {
|
||||
const auto guard = std::make_shared<Guard>(crl::guard(socket, [=] {
|
||||
QMetaObject::invokeMethod(socket, [=] {
|
||||
socket->disconnectFromHost();
|
||||
});
|
||||
}));
|
||||
|
||||
const auto firstLine = socket->readLine().simplified().split(' ');
|
||||
if (firstLine.size() < 2 || firstLine[0] != "GET") {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto headers = [&] {
|
||||
auto result = ::base::flat_map<QByteArray, QByteArray>();
|
||||
while (true) {
|
||||
const auto line = socket->readLine();
|
||||
const auto separator = line.indexOf(':');
|
||||
if (separator <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto name = line.mid(0, separator).simplified();
|
||||
const auto value = line.mid(separator + 1).simplified();
|
||||
result.emplace(name, value);
|
||||
}
|
||||
return result;
|
||||
}();
|
||||
|
||||
const auto getHeader = [&](const QByteArray &key) {
|
||||
const auto it = headers.find(key);
|
||||
return it != headers.end()
|
||||
? it->second
|
||||
: QByteArray();
|
||||
};
|
||||
|
||||
const auto authed = [&] {
|
||||
const auto auth = getHeader("Authorization");
|
||||
if (auth.startsWith("Basic ")) {
|
||||
const auto userPass = QByteArray::fromBase64(auth.mid(6));
|
||||
if (userPass == ':' + password) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
|
||||
if (!authed) {
|
||||
socket->write("HTTP/1.1 401 Unauthorized\r\n");
|
||||
socket->write("WWW-Authenticate: Basic realm=\"\"\r\n");
|
||||
socket->write("\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto id = firstLine[1].mid(1);
|
||||
if (processRedirect(socket, id, headers, guard) || !handler) {
|
||||
return;
|
||||
}
|
||||
|
||||
handler(socket, id, headers, guard);
|
||||
}
|
||||
|
||||
bool HttpServer::Private::processRedirect(
|
||||
QTcpSocket *socket,
|
||||
const QByteArray &id,
|
||||
const ::base::flat_map<QByteArray, QByteArray> &headers,
|
||||
const std::shared_ptr<Guard> &guard) {
|
||||
const auto dot = id.indexOf('.');
|
||||
const auto slash = id.indexOf('/');
|
||||
if (dot < 0 || slash < 0 || dot > slash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto request = QNetworkRequest();
|
||||
request.setUrl(QString::fromUtf8("https://" + id));
|
||||
|
||||
if (!headers.empty()) {
|
||||
const auto headersToCopy = {
|
||||
"Accept",
|
||||
"User-Agent",
|
||||
"Accept-Language",
|
||||
"Accept-Encoding",
|
||||
};
|
||||
for (const auto name : headersToCopy) {
|
||||
const auto it = headers.find(name);
|
||||
if (it == headers.end()) {
|
||||
continue;
|
||||
}
|
||||
request.setRawHeader(name, it->second.constData());
|
||||
}
|
||||
}
|
||||
|
||||
// Always set our own Referer
|
||||
request.setRawHeader("Referer", "http://desktop-app-resource/page.html");
|
||||
|
||||
const auto reply = manager.get(request);
|
||||
connect(socket, &QObject::destroyed, reply, &QObject::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, socket, [=] {
|
||||
(void) guard;
|
||||
const auto input = reply->readAll();
|
||||
socket->write("HTTP/1.1 200 OK\r\n");
|
||||
const auto headersToCopy = {
|
||||
"Content-Type",
|
||||
"Content-Encoding",
|
||||
"Content-Length",
|
||||
};
|
||||
for (const auto name : headersToCopy) {
|
||||
if (!reply->hasRawHeader(name)) {
|
||||
continue;
|
||||
}
|
||||
socket->write(
|
||||
std::format(
|
||||
"{}: {}\r\n",
|
||||
name,
|
||||
reply->rawHeader(name).toStdString()
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
socket->write("Cache-Control: no-store\r\n");
|
||||
socket->write("\r\n");
|
||||
socket->write(input);
|
||||
}, Qt::SingleShotConnection);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
HttpServer::HttpServer(
|
||||
const QByteArray &password,
|
||||
const std::function<void(
|
||||
QTcpSocket *socket,
|
||||
const QByteArray &id,
|
||||
const ::base::flat_map<QByteArray, QByteArray> &headers,
|
||||
const std::shared_ptr<Guard> &guard)> &handler)
|
||||
: _private(std::make_unique<Private>()) {
|
||||
_private->password = password;
|
||||
_private->handler = handler;
|
||||
|
||||
connect(this, &QTcpServer::newConnection, [=] {
|
||||
while (const auto socket = nextPendingConnection()) {
|
||||
connect(
|
||||
socket,
|
||||
&QAbstractSocket::disconnected,
|
||||
socket,
|
||||
&QObject::deleteLater);
|
||||
|
||||
connect(socket, &QIODevice::readyRead, this, [=] {
|
||||
_private->handleRequest(socket);
|
||||
}, Qt::SingleShotConnection);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
HttpServer::~HttpServer() = default;
|
||||
|
||||
} // namespace Webview
|
||||
@@ -0,0 +1,36 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/flat_map.h"
|
||||
|
||||
#include <QtNetwork/QTcpServer>
|
||||
|
||||
#include <gsl/gsl>
|
||||
|
||||
namespace Webview {
|
||||
|
||||
class HttpServer : public QTcpServer {
|
||||
public:
|
||||
using Guard = gsl::final_action<std::function<void()>>;
|
||||
|
||||
HttpServer(
|
||||
const QByteArray &password,
|
||||
const std::function<void(
|
||||
QTcpSocket *socket,
|
||||
const QByteArray &id,
|
||||
const ::base::flat_map<QByteArray, QByteArray> &headers,
|
||||
const std::shared_ptr<Guard> &guard)> &handler);
|
||||
|
||||
~HttpServer();
|
||||
|
||||
private:
|
||||
struct Private;
|
||||
const std::unique_ptr<Private> _private;
|
||||
};
|
||||
|
||||
} // namespace Webview
|
||||
@@ -0,0 +1,74 @@
|
||||
<node>
|
||||
<interface name='org.desktop_app.GtkIntegration.Webview.Master'>
|
||||
<method name='GetStartData'>
|
||||
<arg type='i' name='platform' direction='out'/>
|
||||
<arg type='s' name='waylandDisplay' direction='out'/>
|
||||
<arg type='s' name='appId' direction='out'/>
|
||||
</method>
|
||||
<method name='MessageReceived'>
|
||||
<arg type='ay' name='message' direction='in'/>
|
||||
</method>
|
||||
<method name='NavigationStarted'>
|
||||
<arg type='s' name='uri' direction='in'/>
|
||||
<arg type='b' name='newWindow' direction='in'/>
|
||||
<arg type='b' name='result' direction='out'/>
|
||||
</method>
|
||||
<method name='NavigationDone'>
|
||||
<arg type='b' name='success' direction='in'/>
|
||||
</method>
|
||||
<method name='ScriptDialog'>
|
||||
<arg type='i' name='type' direction='in'/>
|
||||
<arg type='s' name='text' direction='in'/>
|
||||
<arg type='s' name='value' direction='in'/>
|
||||
<arg type='b' name='accepted' direction='out'/>
|
||||
<arg type='s' name='text' direction='out'/>
|
||||
</method>
|
||||
<method name='NavigationStateUpdate'>
|
||||
<arg type='s' name='url' direction='in'/>
|
||||
<arg type='s' name='title' direction='in'/>
|
||||
<arg type='b' name='canGoBack' direction='in'/>
|
||||
<arg type='b' name='canGoForward' direction='in'/>
|
||||
</method>
|
||||
<signal name='DataServerStarted'>
|
||||
<arg type='q' name='port'/>
|
||||
<arg type='s' name='password'/>
|
||||
</signal>
|
||||
</interface>
|
||||
<interface name='org.desktop_app.GtkIntegration.Webview.Helper'>
|
||||
<method name='Create'>
|
||||
<arg type='b' name='debug' direction='in'/>
|
||||
<arg type='i' name='r' direction='in'/>
|
||||
<arg type='i' name='g' direction='in'/>
|
||||
<arg type='i' name='b' direction='in'/>
|
||||
<arg type='i' name='a' direction='in'/>
|
||||
<arg type='s' name='path' direction='in'/>
|
||||
</method>
|
||||
<method name='Reload'/>
|
||||
<method name='Resolve'>
|
||||
<arg type='i' name='result' direction='out'/>
|
||||
</method>
|
||||
<method name='Navigate'>
|
||||
<arg type='s' name='url' direction='in'/>
|
||||
</method>
|
||||
<method name='Resize'>
|
||||
<arg type='i' name='w' direction='in'/>
|
||||
<arg type='i' name='h' direction='in'/>
|
||||
</method>
|
||||
<method name='Init'>
|
||||
<arg type='ay' name='js' direction='in'/>
|
||||
</method>
|
||||
<method name='Eval'>
|
||||
<arg type='ay' name='js' direction='in'/>
|
||||
</method>
|
||||
<method name='SetOpaqueBg'>
|
||||
<arg type='i' name='r' direction='in'/>
|
||||
<arg type='i' name='g' direction='in'/>
|
||||
<arg type='i' name='b' direction='in'/>
|
||||
<arg type='i' name='a' direction='in'/>
|
||||
</method>
|
||||
<method name='GetWinId'>
|
||||
<arg type='t' name='result' direction='out'/>
|
||||
</method>
|
||||
<signal name='Started'/>
|
||||
</interface>
|
||||
</node>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "webview/platform/linux/webview_linux.h"
|
||||
|
||||
namespace Webview::WebKitGTK {
|
||||
|
||||
[[nodiscard]] Available Availability();
|
||||
[[nodiscard]] std::unique_ptr<Interface> CreateInstance(Config config);
|
||||
|
||||
int Exec();
|
||||
void SetSocketPath(const std::string &socketPath);
|
||||
|
||||
} // namespace Webview::WebKitGTK
|
||||
@@ -0,0 +1,107 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "webview/platform/linux/webview_linux_webkitgtk_library.h"
|
||||
|
||||
#include "base/platform/linux/base_linux_library.h"
|
||||
|
||||
namespace Webview::WebKitGTK::Library {
|
||||
|
||||
ResolveResult Resolve(const Platform &platform) {
|
||||
const auto lib = (platform != Platform::X11
|
||||
? base::Platform::LoadLibrary("libwebkitgtk-6.0.so.4", RTLD_NODELETE)
|
||||
: nullptr)
|
||||
?: base::Platform::LoadLibrary("libwebkit2gtk-4.1.so.0", RTLD_NODELETE)
|
||||
?: base::Platform::LoadLibrary("libwebkit2gtk-4.0.so.37", RTLD_NODELETE);
|
||||
const auto result = lib
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, gtk_init_check)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, gtk_widget_get_type)
|
||||
&& (LOAD_LIBRARY_SYMBOL(lib, gtk_window_set_child)
|
||||
|| (LOAD_LIBRARY_SYMBOL(lib, gtk_container_get_type)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, gtk_container_add)))
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, gtk_window_new)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, gtk_scrolled_window_new)
|
||||
&& (LOAD_LIBRARY_SYMBOL(lib, gtk_window_destroy)
|
||||
|| LOAD_LIBRARY_SYMBOL(lib, gtk_widget_destroy))
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, gtk_widget_set_size_request)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, gtk_widget_set_visible)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, gtk_window_get_type)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, gtk_widget_get_display)
|
||||
&& (LOAD_LIBRARY_SYMBOL(lib, gtk_widget_add_css_class)
|
||||
|| (LOAD_LIBRARY_SYMBOL(lib, gtk_widget_get_style_context)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, gtk_style_context_add_class)))
|
||||
&& (LOAD_LIBRARY_SYMBOL(lib, gtk_style_context_add_provider_for_display)
|
||||
|| LOAD_LIBRARY_SYMBOL(lib, gtk_style_context_add_provider_for_screen))
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, gtk_style_provider_get_type)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, gtk_css_provider_new)
|
||||
&& (LOAD_LIBRARY_SYMBOL(lib, gtk_css_provider_load_from_string)
|
||||
|| LOAD_LIBRARY_SYMBOL(lib, gtk_css_provider_load_from_data))
|
||||
&& (platform != Platform::X11
|
||||
|| (LOAD_LIBRARY_SYMBOL(lib, gtk_plug_new)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, gtk_plug_get_id)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, gtk_plug_get_type)))
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, jsc_value_to_string)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_navigation_policy_decision_get_type)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_navigation_policy_decision_get_navigation_action)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_navigation_action_get_request)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_uri_request_get_uri)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_policy_decision_ignore)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_script_dialog_get_dialog_type)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_script_dialog_get_message)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_script_dialog_confirm_set_confirmed)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_script_dialog_prompt_get_default_text)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_script_dialog_prompt_set_text)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_web_view_get_type)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_web_view_get_is_web_process_responsive)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_web_view_get_user_content_manager)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_web_view_get_uri)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_web_view_get_title)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_web_view_can_go_back)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_web_view_can_go_forward)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_user_content_manager_register_script_message_handler)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_web_view_get_settings)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_settings_set_enable_developer_extras)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_web_view_is_loading)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_web_view_load_uri)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_web_view_reload_bypass_cache)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_user_script_new)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_user_content_manager_add_script)
|
||||
&& (LOAD_LIBRARY_SYMBOL(lib, webkit_web_view_evaluate_javascript)
|
||||
|| LOAD_LIBRARY_SYMBOL(lib, webkit_web_view_run_javascript))
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_web_view_set_background_color)
|
||||
&& (LOAD_LIBRARY_SYMBOL(lib, webkit_network_session_new)
|
||||
|| (LOAD_LIBRARY_SYMBOL(lib, webkit_web_view_new_with_context)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_website_data_manager_new)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_web_context_new_with_website_data_manager)))
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_authentication_request_authenticate)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_authentication_request_get_host)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_authentication_request_get_port)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_credential_new)
|
||||
&& LOAD_LIBRARY_SYMBOL(lib, webkit_credential_free);
|
||||
if (!result) {
|
||||
return ResolveResult::NoLibrary;
|
||||
}
|
||||
LOAD_LIBRARY_SYMBOL(lib, gtk_widget_show_all);
|
||||
LOAD_LIBRARY_SYMBOL(lib, gtk_widget_get_screen);
|
||||
LOAD_LIBRARY_SYMBOL(lib, webkit_javascript_result_get_js_value);
|
||||
LOAD_LIBRARY_SYMBOL(lib, webkit_website_data_manager_new);
|
||||
LOAD_LIBRARY_SYMBOL(lib, webkit_web_context_new_with_website_data_manager);
|
||||
if (LOAD_LIBRARY_SYMBOL(lib, gdk_set_allowed_backends)) {
|
||||
switch (platform) {
|
||||
case Platform::Wayland:
|
||||
gdk_set_allowed_backends("wayland");
|
||||
break;
|
||||
case Platform::X11:
|
||||
gdk_set_allowed_backends("x11");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return gtk_init_check(0, 0)
|
||||
? ResolveResult::Success
|
||||
: ResolveResult::CantInit;
|
||||
}
|
||||
|
||||
} // namespace Webview::WebKitGTK::Library
|
||||
@@ -0,0 +1,288 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#define GTK_TYPE_CONTAINER (gtk_container_get_type ())
|
||||
#define GTK_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CONTAINER, GtkContainer))
|
||||
|
||||
#define GTK_TYPE_WIDGET (gtk_widget_get_type ())
|
||||
#define GTK_WIDGET(widget) (G_TYPE_CHECK_INSTANCE_CAST ((widget), GTK_TYPE_WIDGET, GtkWidget))
|
||||
|
||||
#define GTK_TYPE_WINDOW (gtk_window_get_type ())
|
||||
#define GTK_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_WINDOW, GtkWindow))
|
||||
|
||||
#define GTK_TYPE_PLUG (gtk_plug_get_type ())
|
||||
#define GTK_PLUG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_PLUG, GtkPlug))
|
||||
|
||||
#define GTK_TYPE_STYLE_PROVIDER (gtk_style_provider_get_type ())
|
||||
#define GTK_STYLE_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_STYLE_PROVIDER, GtkStyleProvider))
|
||||
#define GTK_STYLE_PROVIDER_PRIORITY_APPLICATION 600
|
||||
|
||||
#define WEBKIT_TYPE_NAVIGATION_POLICY_DECISION (webkit_navigation_policy_decision_get_type())
|
||||
#define WEBKIT_NAVIGATION_POLICY_DECISION(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), WEBKIT_TYPE_NAVIGATION_POLICY_DECISION, WebKitNavigationPolicyDecision))
|
||||
|
||||
#define WEBKIT_TYPE_WEB_VIEW (webkit_web_view_get_type())
|
||||
#define WEBKIT_WEB_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), WEBKIT_TYPE_WEB_VIEW, WebKitWebView))
|
||||
|
||||
struct _GdkRGBA {
|
||||
float red;
|
||||
float green;
|
||||
float blue;
|
||||
float alpha;
|
||||
};
|
||||
|
||||
typedef struct _GdkDisplay GdkDisplay;
|
||||
typedef struct _GdkScreen GdkScreen;
|
||||
typedef struct _GdkRGBA GdkRGBA;
|
||||
typedef struct _GtkContainer GtkContainer;
|
||||
typedef struct _GtkWidget GtkWidget;
|
||||
typedef struct _GtkWindow GtkWindow;
|
||||
typedef struct _GtkPlug GtkPlug;
|
||||
typedef struct _GtkAdjustment GtkAdjustment;
|
||||
typedef struct _GtkStyleContext GtkStyleContext;
|
||||
typedef struct _GtkStyleProvider GtkStyleProvider;
|
||||
typedef struct _GtkCssProvider GtkCssProvider;
|
||||
|
||||
typedef struct _JSCValue JSCValue;
|
||||
|
||||
typedef struct _WebKitJavascriptResult WebKitJavascriptResult;
|
||||
typedef struct _WebKitNavigationAction WebKitNavigationAction;
|
||||
typedef struct _WebKitNavigationPolicyDecision WebKitNavigationPolicyDecision;
|
||||
typedef struct _WebKitPolicyDecision WebKitPolicyDecision;
|
||||
typedef struct _WebKitURIRequest WebKitURIRequest;
|
||||
typedef struct _WebKitUserContentManager WebKitUserContentManager;
|
||||
typedef struct _WebKitUserScript WebKitUserScript;
|
||||
typedef struct _WebKitWebView WebKitWebView;
|
||||
typedef struct _WebKitSettings WebKitSettings;
|
||||
typedef struct _WebKitScriptDialog WebKitScriptDialog;
|
||||
typedef struct _WebKitWebsiteDataManager WebKitWebsiteDataManager;
|
||||
typedef struct _WebKitWebContext WebKitWebContext;
|
||||
typedef struct _WebKitNetworkSession WebKitNetworkSession;
|
||||
typedef struct _WebKitAuthenticationRequest WebKitAuthenticationRequest;
|
||||
typedef struct _WebKitCredential WebKitCredential;
|
||||
|
||||
typedef enum {
|
||||
GTK_WINDOW_TOPLEVEL,
|
||||
GTK_WINDOW_POPUP,
|
||||
} GtkWindowType;
|
||||
|
||||
typedef enum {
|
||||
WEBKIT_WEB_PROCESS_CRASHED,
|
||||
WEBKIT_WEB_PROCESS_EXCEEDED_MEMORY_LIMIT,
|
||||
WEBKIT_WEB_PROCESS_TERMINATED_BY_API,
|
||||
} WebKitWebProcessTerminationReason;
|
||||
|
||||
typedef enum {
|
||||
WEBKIT_LOAD_STARTED,
|
||||
WEBKIT_LOAD_REDIRECTED,
|
||||
WEBKIT_LOAD_COMMITTED,
|
||||
WEBKIT_LOAD_FINISHED,
|
||||
} WebKitLoadEvent;
|
||||
|
||||
typedef enum {
|
||||
WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION,
|
||||
WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION,
|
||||
WEBKIT_POLICY_DECISION_TYPE_RESPONSE,
|
||||
} WebKitPolicyDecisionType;
|
||||
|
||||
typedef enum {
|
||||
WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
|
||||
WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
|
||||
} WebKitUserContentInjectedFrames;
|
||||
|
||||
typedef enum {
|
||||
WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START,
|
||||
WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END,
|
||||
} WebKitUserScriptInjectionTime;
|
||||
|
||||
typedef enum {
|
||||
WEBKIT_SCRIPT_DIALOG_ALERT,
|
||||
WEBKIT_SCRIPT_DIALOG_CONFIRM,
|
||||
WEBKIT_SCRIPT_DIALOG_PROMPT,
|
||||
WEBKIT_SCRIPT_DIALOG_BEFORE_UNLOAD_CONFIRM,
|
||||
} WebKitScriptDialogType;
|
||||
|
||||
typedef enum {
|
||||
WEBKIT_CREDENTIAL_PERSISTENCE_NONE,
|
||||
WEBKIT_CREDENTIAL_PERSISTENCE_FOR_SESSION,
|
||||
WEBKIT_CREDENTIAL_PERSISTENCE_PERMANENT,
|
||||
} WebKitCredentialPersistence;
|
||||
|
||||
namespace Webview::WebKitGTK::Library {
|
||||
|
||||
inline gboolean (*gtk_init_check)(int *argc, char ***argv);
|
||||
inline void (*gdk_set_allowed_backends)(const gchar *backends);
|
||||
inline GType (*gtk_widget_get_type)(void);
|
||||
inline GType (*gtk_container_get_type)(void);
|
||||
inline void (*gtk_container_add)(
|
||||
GtkContainer *container,
|
||||
GtkWidget *widget);
|
||||
inline void (*gtk_window_set_child)(
|
||||
GtkWindow *window,
|
||||
GtkWidget *child);
|
||||
inline GtkWidget *(*gtk_window_new)(GtkWindowType type);
|
||||
inline GtkWidget *(*gtk_scrolled_window_new)(
|
||||
GtkAdjustment *hadjustment,
|
||||
GtkAdjustment *vadjustment);
|
||||
inline void (*gtk_window_destroy)(GtkWindow *widget);
|
||||
inline void (*gtk_widget_destroy)(GtkWidget *widget);
|
||||
inline void (*gtk_widget_set_size_request)(
|
||||
GtkWidget *window,
|
||||
gint width,
|
||||
gint height);
|
||||
inline void (*gtk_widget_set_visible)(GtkWidget *widget, gboolean visible);
|
||||
inline void (*gtk_widget_show_all)(GtkWidget *widget);
|
||||
inline GType (*gtk_window_get_type)(void);
|
||||
inline GdkDisplay *(*gtk_widget_get_display)(GtkWidget *widget);
|
||||
inline GdkScreen *(*gtk_widget_get_screen)(GtkWidget *widget);
|
||||
inline GtkStyleContext *(*gtk_widget_get_style_context)(GtkWidget *widget);
|
||||
inline void (*gtk_widget_add_css_class)(
|
||||
GtkWidget *widget,
|
||||
const char *css_class);
|
||||
inline void (*gtk_style_context_add_provider_for_display)(
|
||||
GdkDisplay *display,
|
||||
GtkStyleProvider *provider,
|
||||
guint priority);
|
||||
inline void (*gtk_style_context_add_provider_for_screen)(
|
||||
GdkScreen *screen,
|
||||
GtkStyleProvider *provider,
|
||||
guint priority);
|
||||
inline void (*gtk_style_context_add_class)(
|
||||
GtkStyleContext *context,
|
||||
const char *class_name);
|
||||
inline GType (*gtk_style_provider_get_type)(void);
|
||||
inline GtkCssProvider *(*gtk_css_provider_new)(void);
|
||||
inline void (*gtk_css_provider_load_from_string)(
|
||||
GtkCssProvider *css_provider,
|
||||
const char *string);
|
||||
inline void (*gtk_css_provider_load_from_data)(
|
||||
GtkCssProvider *css_provider,
|
||||
const gchar *data,
|
||||
gssize length,
|
||||
GError **error);
|
||||
|
||||
// returns Window that is a typedef to unsigned long,
|
||||
// but we avoid to include Xlib.h here
|
||||
inline GtkWidget *(*gtk_plug_new)(unsigned long socket_id);
|
||||
inline unsigned long (*gtk_plug_get_id)(GtkPlug *plug);
|
||||
inline GType (*gtk_plug_get_type)(void);
|
||||
|
||||
inline char *(*jsc_value_to_string)(JSCValue *value);
|
||||
inline JSCValue *(*webkit_javascript_result_get_js_value)(
|
||||
WebKitJavascriptResult *js_result);
|
||||
|
||||
inline GType (*webkit_navigation_policy_decision_get_type)(void);
|
||||
inline WebKitNavigationAction *(*webkit_navigation_policy_decision_get_navigation_action)(
|
||||
WebKitNavigationPolicyDecision *decision);
|
||||
inline WebKitURIRequest *(*webkit_navigation_action_get_request)(
|
||||
WebKitNavigationAction *navigation);
|
||||
inline const gchar *(*webkit_uri_request_get_uri)(WebKitURIRequest *request);
|
||||
inline void (*webkit_policy_decision_ignore)(WebKitPolicyDecision *decision);
|
||||
|
||||
inline WebKitScriptDialogType (*webkit_script_dialog_get_dialog_type)(
|
||||
WebKitScriptDialog *dialog);
|
||||
inline const gchar *(*webkit_script_dialog_get_message)(
|
||||
WebKitScriptDialog *dialog);
|
||||
inline void (*webkit_script_dialog_confirm_set_confirmed)(
|
||||
WebKitScriptDialog *dialog,
|
||||
gboolean confirmed);
|
||||
inline const gchar *(*webkit_script_dialog_prompt_get_default_text)(
|
||||
WebKitScriptDialog *dialog);
|
||||
inline void (*webkit_script_dialog_prompt_set_text)(
|
||||
WebKitScriptDialog *dialog,
|
||||
const gchar *text);
|
||||
|
||||
inline GtkWidget *(*webkit_web_view_new_with_context)(WebKitWebContext *context);
|
||||
inline GType (*webkit_web_view_get_type)(void);
|
||||
inline gboolean (*webkit_web_view_get_is_web_process_responsive)(
|
||||
WebKitWebView *web_view);
|
||||
inline WebKitUserContentManager *(*webkit_web_view_get_user_content_manager)(
|
||||
WebKitWebView *web_view);
|
||||
inline const gchar *(*webkit_web_view_get_uri)(WebKitWebView *web_view);
|
||||
inline const gchar *(*webkit_web_view_get_title)(WebKitWebView *web_view);
|
||||
inline gboolean (*webkit_web_view_can_go_back)(WebKitWebView *web_view);
|
||||
inline gboolean (*webkit_web_view_can_go_forward)(WebKitWebView *web_view);
|
||||
inline gboolean (*webkit_user_content_manager_register_script_message_handler)(
|
||||
WebKitUserContentManager *manager,
|
||||
const gchar *name,
|
||||
const gchar *world_name);
|
||||
inline WebKitSettings *(*webkit_web_view_get_settings)(
|
||||
WebKitWebView *web_view);
|
||||
inline void (*webkit_settings_set_enable_developer_extras)(
|
||||
WebKitSettings *settings,
|
||||
gboolean enabled);
|
||||
inline gboolean (*webkit_web_view_is_loading)(WebKitWebView *web_view);
|
||||
inline void (*webkit_web_view_load_uri)(
|
||||
WebKitWebView *web_view,
|
||||
const gchar *uri);
|
||||
inline void (*webkit_web_view_reload_bypass_cache)(WebKitWebView *web_view);
|
||||
inline WebKitUserScript *(*webkit_user_script_new)(
|
||||
const gchar *source,
|
||||
WebKitUserContentInjectedFrames injected_frames,
|
||||
WebKitUserScriptInjectionTime injection_time,
|
||||
const gchar *const *whitelist,
|
||||
const gchar *const *blacklist);
|
||||
inline void (*webkit_user_content_manager_add_script)(
|
||||
WebKitUserContentManager *manager,
|
||||
WebKitUserScript *script);
|
||||
inline void (*webkit_web_view_evaluate_javascript)(
|
||||
WebKitWebView *web_view,
|
||||
const gchar *script,
|
||||
gssize length,
|
||||
const gchar *world_name,
|
||||
const gchar *source_uri,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
inline void (*webkit_web_view_run_javascript)(
|
||||
WebKitWebView *web_view,
|
||||
const gchar *script,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
inline void (*webkit_web_view_set_background_color)(
|
||||
WebKitWebView *web_view,
|
||||
const GdkRGBA *rgba);
|
||||
inline WebKitWebsiteDataManager *(*webkit_website_data_manager_new)(
|
||||
const gchar *first_option_name,
|
||||
...);
|
||||
inline WebKitWebContext *(*webkit_web_context_new_with_website_data_manager)(
|
||||
WebKitWebsiteDataManager* manager);
|
||||
inline WebKitNetworkSession *(*webkit_network_session_new)(
|
||||
const char* data_directory,
|
||||
const char* cache_directory);
|
||||
inline void (*webkit_authentication_request_authenticate)(
|
||||
WebKitAuthenticationRequest *request,
|
||||
WebKitCredential *credential);
|
||||
inline const gchar *(*webkit_authentication_request_get_host)(
|
||||
WebKitAuthenticationRequest *request);
|
||||
inline guint (*webkit_authentication_request_get_port)(
|
||||
WebKitAuthenticationRequest *request);
|
||||
inline WebKitCredential *(*webkit_credential_new)(
|
||||
const gchar *username,
|
||||
const gchar *password,
|
||||
WebKitCredentialPersistence persistence);
|
||||
inline void (*webkit_credential_free)(WebKitCredential *credential);
|
||||
|
||||
enum class ResolveResult {
|
||||
Success,
|
||||
NoLibrary,
|
||||
CantInit,
|
||||
IPCFailure,
|
||||
};
|
||||
|
||||
enum class Platform {
|
||||
Any,
|
||||
Wayland,
|
||||
X11,
|
||||
};
|
||||
|
||||
[[nodiscard]] ResolveResult Resolve(const Platform &platform);
|
||||
|
||||
} // namespace Webview::WebKitGTK::Library
|
||||
9
Telegram/lib_webview/webview/platform/mac/webview_mac.h
Normal file
9
Telegram/lib_webview/webview/platform/mac/webview_mac.h
Normal file
@@ -0,0 +1,9 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "webview/webview_interface.h"
|
||||
922
Telegram/lib_webview/webview/platform/mac/webview_mac.mm
Normal file
922
Telegram/lib_webview/webview/platform/mac/webview_mac.mm
Normal file
@@ -0,0 +1,922 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "webview/platform/mac/webview_mac.h"
|
||||
|
||||
#include "webview/webview_data_stream.h"
|
||||
#include "webview/webview_data_stream_memory.h"
|
||||
#include "base/algorithm.h"
|
||||
#include "base/debug_log.h"
|
||||
#include "base/unique_qptr.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "base/flat_map.h"
|
||||
|
||||
#include <crl/crl_on_main.h>
|
||||
#include <crl/crl_time.h>
|
||||
#include <rpl/rpl.h>
|
||||
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtGui/QDesktopServices>
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kDataUrlScheme = std::string_view("desktop-app-resource");
|
||||
constexpr auto kFullDomain = std::string_view("desktop-app-resource://domain/");
|
||||
constexpr auto kPartsCacheLimit = 32 * 1024 * 1024;
|
||||
constexpr auto kUuidSize = 16;
|
||||
|
||||
using TaskPointer = id<WKURLSchemeTask>;
|
||||
|
||||
[[nodiscard]] NSString *stdToNS(std::string_view value) {
|
||||
return [[NSString alloc]
|
||||
initWithBytes:value.data()
|
||||
length:value.length()
|
||||
encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
[[nodiscard]] std::unique_ptr<char[]> WrapBytes(const char *data, int64 length) {
|
||||
Expects(length > 0);
|
||||
|
||||
auto result = std::unique_ptr<char[]>(new char[length]);
|
||||
memcpy(result.get(), data, length);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@interface Handler : NSObject<WKScriptMessageHandler, WKNavigationDelegate, WKUIDelegate, WKURLSchemeHandler> {
|
||||
}
|
||||
|
||||
- (id) initWithMessageHandler:(std::function<void(std::string)>)messageHandler navigationStartHandler:(std::function<bool(std::string,bool)>)navigationStartHandler navigationDoneHandler:(std::function<void(bool)>)navigationDoneHandler dialogHandler:(std::function<Webview::DialogResult(Webview::DialogArgs)>)dialogHandler dataRequested:(std::function<void(id<WKURLSchemeTask>,bool)>)dataRequested updateStates:(std::function<void()>)updateStates dataDomain:(std::string)dataDomain;
|
||||
- (void) userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
|
||||
- (void) webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
|
||||
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context;
|
||||
- (void) webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation;
|
||||
- (void) webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error;
|
||||
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures;
|
||||
- (void) webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray<NSURL *> * _Nullable URLs))completionHandler;
|
||||
- (void) webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler;
|
||||
- (void) webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler;
|
||||
- (void) webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *result))completionHandler;
|
||||
- (void) webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)task;
|
||||
- (void) webView:(WKWebView *)webView stopURLSchemeTask:(id <WKURLSchemeTask>)task;
|
||||
- (void) dealloc;
|
||||
|
||||
@end // @interface Handler
|
||||
|
||||
@implementation Handler {
|
||||
std::function<void(std::string)> _messageHandler;
|
||||
std::function<bool(std::string,bool)> _navigationStartHandler;
|
||||
std::function<void(bool)> _navigationDoneHandler;
|
||||
std::function<Webview::DialogResult(Webview::DialogArgs)> _dialogHandler;
|
||||
std::function<void(id<WKURLSchemeTask> task, bool started)> _dataRequested;
|
||||
std::function<void()> _updateStates;
|
||||
std::string _dataDomain;
|
||||
base::flat_map<TaskPointer, NSURLSessionDataTask*> _redirectedTasks;
|
||||
base::has_weak_ptr _guard;
|
||||
}
|
||||
|
||||
- (id) initWithMessageHandler:(std::function<void(std::string)>)messageHandler navigationStartHandler:(std::function<bool(std::string,bool)>)navigationStartHandler navigationDoneHandler:(std::function<void(bool)>)navigationDoneHandler dialogHandler:(std::function<Webview::DialogResult(Webview::DialogArgs)>)dialogHandler dataRequested:(std::function<void(id<WKURLSchemeTask>,bool)>)dataRequested updateStates:(std::function<void()>)updateStates dataDomain:(std::string)dataDomain {
|
||||
if (self = [super init]) {
|
||||
_messageHandler = std::move(messageHandler);
|
||||
_navigationStartHandler = std::move(navigationStartHandler);
|
||||
_navigationDoneHandler = std::move(navigationDoneHandler);
|
||||
_dialogHandler = std::move(dialogHandler);
|
||||
_dataRequested = std::move(dataRequested);
|
||||
_updateStates = std::move(updateStates);
|
||||
_dataDomain = std::move(dataDomain);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) userContentController:(WKUserContentController *)userContentController
|
||||
didReceiveScriptMessage:(WKScriptMessage *)message {
|
||||
id body = [message body];
|
||||
if ([body isKindOfClass:[NSString class]]) {
|
||||
NSString *string = (NSString*)body;
|
||||
_messageHandler([string UTF8String]);
|
||||
}
|
||||
}
|
||||
|
||||
- (void) webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
|
||||
NSString *string = [[[navigationAction request] URL] absoluteString];
|
||||
WKFrameInfo *target = [navigationAction targetFrame];
|
||||
const auto newWindow = !target;
|
||||
const auto url = [string UTF8String];
|
||||
if (newWindow) {
|
||||
if (_navigationStartHandler && _navigationStartHandler(url, true)) {
|
||||
QDesktopServices::openUrl(QString::fromUtf8(url));
|
||||
}
|
||||
decisionHandler(WKNavigationActionPolicyCancel);
|
||||
} else {
|
||||
if ([target isMainFrame]
|
||||
&& !std::string(url).starts_with(_dataDomain)
|
||||
&& _navigationStartHandler
|
||||
&& !_navigationStartHandler(url, false)) {
|
||||
decisionHandler(WKNavigationActionPolicyCancel);
|
||||
} else {
|
||||
decisionHandler(WKNavigationActionPolicyAllow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
|
||||
if ([keyPath isEqualToString:@"URL"] || [keyPath isEqualToString:@"title"]) {
|
||||
if (_updateStates) {
|
||||
_updateStates();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
|
||||
if (_navigationDoneHandler) {
|
||||
_navigationDoneHandler(true);
|
||||
}
|
||||
if (_updateStates) {
|
||||
_updateStates();
|
||||
}
|
||||
}
|
||||
|
||||
- (void) webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
|
||||
if (_navigationDoneHandler) {
|
||||
_navigationDoneHandler(false);
|
||||
}
|
||||
if (_updateStates) {
|
||||
_updateStates();
|
||||
}
|
||||
}
|
||||
|
||||
- (nullable WKWebView *) webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
|
||||
NSString *string = [[[navigationAction request] URL] absoluteString];
|
||||
const auto url = [string UTF8String];
|
||||
if (_navigationStartHandler && _navigationStartHandler(url, true)) {
|
||||
QDesktopServices::openUrl(QString::fromUtf8(url));
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void) webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray<NSURL *> * _Nullable URLs))completionHandler {
|
||||
|
||||
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
|
||||
|
||||
if (@available(macOS 10.13.4, *)) {
|
||||
[openPanel setCanChooseDirectories:parameters.allowsDirectories];
|
||||
}
|
||||
[openPanel setCanChooseFiles:YES];
|
||||
[openPanel setAllowsMultipleSelection:parameters.allowsMultipleSelection];
|
||||
[openPanel setResolvesAliases:YES];
|
||||
|
||||
[openPanel beginWithCompletionHandler:^(NSInteger result){
|
||||
if (result == NSModalResponseOK) {
|
||||
completionHandler([openPanel URLs]);
|
||||
} else {
|
||||
completionHandler(nil);
|
||||
}
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
- (void) webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
|
||||
auto text = [message UTF8String];
|
||||
auto uri = [[[frame request] URL] absoluteString];
|
||||
auto url = [uri UTF8String];
|
||||
const auto result = _dialogHandler(Webview::DialogArgs{
|
||||
.type = Webview::DialogType::Alert,
|
||||
.text = text,
|
||||
.url = url,
|
||||
});
|
||||
completionHandler();
|
||||
}
|
||||
|
||||
- (void) webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {
|
||||
auto text = [message UTF8String];
|
||||
auto uri = [[[frame request] URL] absoluteString];
|
||||
auto url = [uri UTF8String];
|
||||
const auto result = _dialogHandler(Webview::DialogArgs{
|
||||
.type = Webview::DialogType::Confirm,
|
||||
.text = text,
|
||||
.url = url,
|
||||
});
|
||||
completionHandler(result.accepted ? YES : NO);
|
||||
}
|
||||
|
||||
- (void) webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *result))completionHandler {
|
||||
auto text = [prompt UTF8String];
|
||||
auto value = [defaultText UTF8String];
|
||||
auto uri = [[[frame request] URL] absoluteString];
|
||||
auto url = [uri UTF8String];
|
||||
const auto result = _dialogHandler(Webview::DialogArgs{
|
||||
.type = Webview::DialogType::Prompt,
|
||||
.value = value,
|
||||
.text = text,
|
||||
.url = url,
|
||||
});
|
||||
if (result.accepted) {
|
||||
completionHandler([NSString stringWithUTF8String:result.text.c_str()]);
|
||||
} else {
|
||||
completionHandler(nil);
|
||||
}
|
||||
}
|
||||
|
||||
- (void) webView:(WKWebView *)webView startURLSchemeTask:(id<WKURLSchemeTask>)task {
|
||||
if (![self processRedirect:task]) {
|
||||
_dataRequested(task, true);
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL) processRedirect:(id<WKURLSchemeTask>)task {
|
||||
NSString *url = task.request.URL.absoluteString;
|
||||
NSString *prefix = stdToNS(_dataDomain);
|
||||
NSString *resource = [url substringFromIndex:[prefix length]];
|
||||
const auto id = std::string([resource UTF8String]);
|
||||
const auto dot = id.find_first_of('.');
|
||||
const auto slash = id.find_first_of('/');
|
||||
if (dot == std::string::npos
|
||||
|| slash == std::string::npos
|
||||
|| dot > slash) {
|
||||
return NO;
|
||||
}
|
||||
NSMutableURLRequest *redirected = [task.request mutableCopy];
|
||||
redirected.URL = [NSURL URLWithString:[@"https://" stringByAppendingString:resource]];
|
||||
[redirected
|
||||
setValue:@"http://desktop-app-resource/page.html"
|
||||
forHTTPHeaderField:@"Referer"];
|
||||
|
||||
const auto weak = base::make_weak(&_guard);
|
||||
|
||||
NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession]
|
||||
dataTaskWithRequest:redirected
|
||||
completionHandler:^(
|
||||
NSData * _Nullable data,
|
||||
NSURLResponse * _Nullable response,
|
||||
NSError * _Nullable error) {
|
||||
if (response) [response retain];
|
||||
if (error) [error retain];
|
||||
if (data) [data retain];
|
||||
crl::on_main([=] {
|
||||
if (weak) {
|
||||
const auto i = _redirectedTasks.find(task);
|
||||
if (i == end(_redirectedTasks)) {
|
||||
return;
|
||||
}
|
||||
NSURLSessionDataTask *dataTask = i->second;
|
||||
_redirectedTasks.erase(i);
|
||||
|
||||
if (error) {
|
||||
[task didFailWithError:error];
|
||||
} else {
|
||||
[task didReceiveResponse:response];
|
||||
[task didReceiveData:data];
|
||||
[task didFinish];
|
||||
}
|
||||
[task release];
|
||||
[dataTask release];
|
||||
}
|
||||
if (response) [response release];
|
||||
if (error) [error release];
|
||||
if (data) [data release];
|
||||
});
|
||||
}];
|
||||
|
||||
[task retain];
|
||||
[dataTask retain];
|
||||
_redirectedTasks.emplace(task, dataTask);
|
||||
|
||||
[dataTask resume];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void) webView:(WKWebView *)webView stopURLSchemeTask:(id <WKURLSchemeTask>)task {
|
||||
const auto i = _redirectedTasks.find(task);
|
||||
if (i != end(_redirectedTasks)) {
|
||||
NSURLSessionDataTask *dataTask = i->second;
|
||||
_redirectedTasks.erase(i);
|
||||
|
||||
[task release];
|
||||
[dataTask cancel];
|
||||
[dataTask release];
|
||||
} else {
|
||||
_dataRequested(task, false);
|
||||
}
|
||||
}
|
||||
|
||||
- (void) dealloc {
|
||||
for (const auto &[task, dataTask] : base::take(_redirectedTasks)) {
|
||||
NSError *error = [NSError
|
||||
errorWithDomain:@"org.telegram.desktop"
|
||||
code:404
|
||||
userInfo:nil];
|
||||
[task didFailWithError:error];
|
||||
[task release];
|
||||
[dataTask cancel];
|
||||
[dataTask release];
|
||||
}
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end // @implementation Handler
|
||||
|
||||
namespace Webview {
|
||||
namespace {
|
||||
|
||||
class Instance final : public Interface, public base::has_weak_ptr {
|
||||
public:
|
||||
explicit Instance(Config config);
|
||||
~Instance();
|
||||
|
||||
void navigate(std::string url) override;
|
||||
void navigateToData(std::string id) override;
|
||||
void reload() override;
|
||||
|
||||
void init(std::string js) override;
|
||||
void eval(std::string js) override;
|
||||
|
||||
void focus() override;
|
||||
|
||||
QWidget *widget() override;
|
||||
|
||||
void refreshNavigationHistoryState() override;
|
||||
auto navigationHistoryState()
|
||||
-> rpl::producer<NavigationHistoryState> override;
|
||||
|
||||
void setOpaqueBg(QColor opaqueBg) override;
|
||||
|
||||
private:
|
||||
struct Task {
|
||||
int index = 0;
|
||||
crl::time started = 0;
|
||||
};
|
||||
struct PartialResource {
|
||||
uint32 index = 0;
|
||||
uint32 total = 0;
|
||||
std::string mime;
|
||||
};
|
||||
struct PartData {
|
||||
std::unique_ptr<char[]> bytes;
|
||||
int64 length = 0;
|
||||
};
|
||||
struct CachedResult {
|
||||
std::string mime;
|
||||
NSData *data = nil;
|
||||
int64 requestFrom = 0;
|
||||
int64 requestLength = 0;
|
||||
int64 total = 0;
|
||||
|
||||
explicit operator bool() const {
|
||||
return data != nil;
|
||||
}
|
||||
};
|
||||
using CacheKey = uint64;
|
||||
|
||||
static void TaskFail(TaskPointer task);
|
||||
void taskFail(TaskPointer task, int indexToCheck);
|
||||
void taskDone(
|
||||
TaskPointer task,
|
||||
int indexToCheck,
|
||||
const std::string &mime,
|
||||
NSData *data,
|
||||
int64 offset,
|
||||
int64 total);
|
||||
|
||||
void processDataRequest(TaskPointer task, bool started);
|
||||
|
||||
[[nodiscard]] CachedResult fillFromCache(const DataRequest &request);
|
||||
void addToCache(uint32 resourceIndex, int64 offset, PartData data);
|
||||
void removeCacheEntry(CacheKey key);
|
||||
void pruneCache();
|
||||
|
||||
void updateHistoryStates();
|
||||
|
||||
[[nodiscard]] static CacheKey KeyFromValues(
|
||||
uint32 resourceIndex,
|
||||
int64 offset);
|
||||
[[nodiscard]] static uint32 ResourceIndexFromKey(CacheKey key);
|
||||
[[nodiscard]] static int64 OffsetFromKey(CacheKey key);
|
||||
|
||||
WKUserContentController *_manager = nullptr;
|
||||
WKWebView *_webview = nullptr;
|
||||
Handler *_handler = nullptr;
|
||||
base::unique_qptr<QWindow> _window;
|
||||
base::unique_qptr<QWidget> _widget;
|
||||
std::string _dataProtocol;
|
||||
std::string _dataDomain;
|
||||
std::function<DataResult(DataRequest)> _dataRequestHandler;
|
||||
rpl::variable<NavigationHistoryState> _navigationHistoryState;
|
||||
|
||||
base::flat_map<TaskPointer, Task> _tasks;
|
||||
base::flat_map<std::string, PartialResource> _partialResources;
|
||||
base::flat_map<CacheKey, PartData> _partsCache;
|
||||
std::vector<CacheKey> _partsLRU;
|
||||
int64 _cacheTotal = 0;
|
||||
int _taskAutoincrement = 0;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] NSUUID *UuidFromToken(const std::string &token) {
|
||||
const auto bytes = reinterpret_cast<const unsigned char*>(token.data());
|
||||
return (token.size() == kUuidSize)
|
||||
? [[NSUUID alloc] initWithUUIDBytes:bytes]
|
||||
: nil;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string UuidToToken(NSUUID *uuid) {
|
||||
if (!uuid) {
|
||||
return std::string();
|
||||
}
|
||||
auto result = std::string(kUuidSize, ' ');
|
||||
const auto bytes = reinterpret_cast<unsigned char*>(result.data());
|
||||
[uuid getUUIDBytes:bytes];
|
||||
return result;
|
||||
}
|
||||
|
||||
Instance::Instance(Config config) {
|
||||
const auto weak = base::make_weak(this);
|
||||
const auto handleDataRequest = [=](id<WKURLSchemeTask> task, bool started) {
|
||||
if (weak) {
|
||||
processDataRequest(task, started);
|
||||
} else if (started) {
|
||||
TaskFail(task);
|
||||
}
|
||||
};
|
||||
|
||||
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
|
||||
_manager = configuration.userContentController;
|
||||
_dataProtocol = kDataUrlScheme;
|
||||
_dataDomain = kFullDomain;
|
||||
if (!config.dataProtocolOverride.empty()) {
|
||||
_dataProtocol = config.dataProtocolOverride;
|
||||
_dataDomain = _dataProtocol + "://domain/";
|
||||
}
|
||||
if (config.debug) {
|
||||
[configuration.preferences setValue:@YES forKey:@"developerExtrasEnabled"];
|
||||
}
|
||||
if (config.safe) {
|
||||
[configuration.preferences setValue:@NO forKey:@"fraudulentWebsiteWarningEnabled"];
|
||||
}
|
||||
const auto updateStates = [=] {
|
||||
updateHistoryStates();
|
||||
};
|
||||
_handler = [[Handler alloc] initWithMessageHandler:config.messageHandler navigationStartHandler:config.navigationStartHandler navigationDoneHandler:config.navigationDoneHandler dialogHandler:config.dialogHandler dataRequested:handleDataRequest updateStates:updateStates dataDomain:_dataDomain];
|
||||
_dataRequestHandler = std::move(config.dataRequestHandler);
|
||||
[configuration setURLSchemeHandler:_handler forURLScheme:stdToNS(_dataProtocol)];
|
||||
if (@available(macOS 14, *)) {
|
||||
if (config.userDataToken != LegacyStorageIdToken().toStdString()) {
|
||||
NSUUID *uuid = UuidFromToken(config.userDataToken);
|
||||
[configuration setWebsiteDataStore:[WKWebsiteDataStore dataStoreForIdentifier:uuid]];
|
||||
[uuid release];
|
||||
}
|
||||
}
|
||||
_webview = [[WKWebView alloc] initWithFrame:NSZeroRect configuration:configuration];
|
||||
if (@available(macOS 13.3, *)) {
|
||||
_webview.inspectable = config.debug ? YES : NO;
|
||||
}
|
||||
[_manager addScriptMessageHandler:_handler name:@"external"];
|
||||
[_webview setNavigationDelegate:_handler];
|
||||
[_webview setUIDelegate:_handler];
|
||||
|
||||
[_webview addObserver:_handler forKeyPath:@"URL" options:NSKeyValueObservingOptionNew context:nil];
|
||||
[_webview addObserver:_handler forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil];
|
||||
|
||||
[configuration release];
|
||||
|
||||
_window.reset(QWindow::fromWinId(WId(_webview)));
|
||||
_widget.reset();
|
||||
|
||||
_widget.reset(
|
||||
QWidget::createWindowContainer(
|
||||
_window.get(),
|
||||
config.parent,
|
||||
Qt::FramelessWindowHint));
|
||||
_widget->show();
|
||||
|
||||
setOpaqueBg(config.opaqueBg);
|
||||
init(R"(
|
||||
window.external = {
|
||||
invoke: function(s) {
|
||||
window.webkit.messageHandlers.external.postMessage(s);
|
||||
}
|
||||
};)");
|
||||
}
|
||||
|
||||
Instance::~Instance() {
|
||||
base::take(_window);
|
||||
base::take(_widget);
|
||||
[_manager removeScriptMessageHandlerForName:@"external"];
|
||||
[_webview setNavigationDelegate:nil];
|
||||
[_handler release];
|
||||
[_webview release];
|
||||
}
|
||||
|
||||
void Instance::TaskFail(TaskPointer task) {
|
||||
[task didFailWithError:[NSError errorWithDomain:@"org.telegram.desktop" code:404 userInfo:nil]];
|
||||
}
|
||||
|
||||
void Instance::taskFail(TaskPointer task, int indexToCheck) {
|
||||
if (indexToCheck) {
|
||||
const auto i = _tasks.find(task);
|
||||
if (i == end(_tasks) || i->second.index != indexToCheck) {
|
||||
return;
|
||||
}
|
||||
_tasks.erase(i);
|
||||
}
|
||||
TaskFail(task);
|
||||
}
|
||||
|
||||
void Instance::taskDone(
|
||||
TaskPointer task,
|
||||
int indexToCheck,
|
||||
const std::string &mime,
|
||||
NSData *data,
|
||||
int64 offset,
|
||||
int64 total) {
|
||||
Expects(data != nil);
|
||||
|
||||
if (indexToCheck) {
|
||||
const auto i = _tasks.find(task);
|
||||
if (i == end(_tasks) || i->second.index != indexToCheck) {
|
||||
return;
|
||||
}
|
||||
_tasks.erase(i);
|
||||
}
|
||||
|
||||
const auto length = int64([data length]);
|
||||
const auto partial = (offset > 0) || (total != length);
|
||||
NSMutableDictionary *headers = [@{
|
||||
@"Content-Type": stdToNS(mime),
|
||||
@"Accept-Ranges": @"bytes",
|
||||
@"Cache-Control": @"no-store",
|
||||
@"Content-Length": stdToNS(std::to_string(length)),
|
||||
} mutableCopy];
|
||||
if (partial) {
|
||||
headers[@"Content-Range"] = stdToNS("bytes "
|
||||
+ std::to_string(offset)
|
||||
+ '-'
|
||||
+ std::to_string(offset + length - 1)
|
||||
+ '/'
|
||||
+ std::to_string(total));
|
||||
}
|
||||
NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc]
|
||||
initWithURL:task.request.URL
|
||||
statusCode:(partial ? 206 : 200)
|
||||
HTTPVersion:@"HTTP/1.1"
|
||||
headerFields:headers];
|
||||
|
||||
[task didReceiveResponse:response];
|
||||
[task didReceiveData:data];
|
||||
[task didFinish];
|
||||
}
|
||||
|
||||
Instance::CacheKey Instance::KeyFromValues(uint32 resourceIndex, int64 offset) {
|
||||
return (uint64(resourceIndex) << 32) | uint32(offset);
|
||||
}
|
||||
|
||||
uint32 Instance::ResourceIndexFromKey(CacheKey key) {
|
||||
return uint32(key >> 32);
|
||||
}
|
||||
|
||||
int64 Instance::OffsetFromKey(CacheKey key) {
|
||||
return int64(key & 0xFFFFFFFFULL);
|
||||
}
|
||||
|
||||
void Instance::addToCache(uint32 resourceIndex, int64 offset, PartData data) {
|
||||
auto key = KeyFromValues(resourceIndex, offset);
|
||||
while (true) { // Remove parts that are already in cache.
|
||||
auto i = _partsCache.upper_bound(key);
|
||||
if (i != begin(_partsCache)) {
|
||||
--i;
|
||||
const auto alreadyIndex = ResourceIndexFromKey(i->first);
|
||||
if (alreadyIndex == resourceIndex) {
|
||||
const auto &already = i->second;
|
||||
const auto alreadyOffset = OffsetFromKey(i->first);
|
||||
const auto alreadyTill = alreadyOffset + already.length;
|
||||
if (alreadyTill >= offset + data.length) {
|
||||
return; // Fully in cache.
|
||||
} else if (alreadyTill > offset) {
|
||||
const auto delta = alreadyTill - offset;
|
||||
offset += delta;
|
||||
data.length -= delta;
|
||||
data.bytes = WrapBytes(data.bytes.get() + delta, data.length);
|
||||
key = KeyFromValues(resourceIndex, offset);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
++i;
|
||||
}
|
||||
if (i != end(_partsCache)) {
|
||||
const auto alreadyIndex = ResourceIndexFromKey(i->first);
|
||||
if (alreadyIndex == resourceIndex) {
|
||||
const auto &already = i->second;
|
||||
const auto alreadyOffset = OffsetFromKey(i->first);
|
||||
Assert(alreadyOffset > offset);
|
||||
const auto alreadyTill = alreadyOffset + already.length;
|
||||
if (alreadyTill <= offset + data.length) {
|
||||
removeCacheEntry(i->first);
|
||||
continue;
|
||||
} else if (alreadyOffset < offset + data.length) {
|
||||
const auto delta = offset + data.length - alreadyOffset;
|
||||
data.length -= delta;
|
||||
data.bytes = WrapBytes(data.bytes.get(), data.length);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
_partsLRU.push_back(key);
|
||||
_cacheTotal += data.length;
|
||||
_partsCache[key] = std::move(data);
|
||||
pruneCache();
|
||||
}
|
||||
|
||||
void Instance::pruneCache() {
|
||||
while (_cacheTotal > kPartsCacheLimit) {
|
||||
Assert(!_partsLRU.empty());
|
||||
removeCacheEntry(_partsLRU.front());
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::updateHistoryStates() {
|
||||
NSURL *maybeUrl = [_webview URL];
|
||||
NSString *maybeTitle = [_webview title];
|
||||
const auto url = maybeUrl
|
||||
? std::string([[maybeUrl absoluteString] UTF8String])
|
||||
: std::string();
|
||||
const auto title = maybeTitle
|
||||
? std::string([maybeTitle UTF8String])
|
||||
: std::string();
|
||||
_navigationHistoryState = NavigationHistoryState{
|
||||
.url = url,
|
||||
.title = title,
|
||||
.canGoBack = ([_webview canGoBack] == YES),
|
||||
.canGoForward = ([_webview canGoForward] == YES),
|
||||
};
|
||||
}
|
||||
|
||||
void Instance::removeCacheEntry(CacheKey key) {
|
||||
auto &part = _partsCache[key];
|
||||
Assert(part.length > 0);
|
||||
Assert(_cacheTotal >= part.length);
|
||||
_cacheTotal -= part.length;
|
||||
_partsCache.remove(key);
|
||||
_partsLRU.erase(
|
||||
std::remove(begin(_partsLRU), end(_partsLRU), key),
|
||||
end(_partsLRU));
|
||||
}
|
||||
|
||||
Instance::CachedResult Instance::fillFromCache(
|
||||
const DataRequest &request) {
|
||||
auto &partial = _partialResources[request.id];
|
||||
const auto index = partial.index;
|
||||
if (!index) {
|
||||
partial.index = uint32(_partialResources.size());
|
||||
return {};
|
||||
}
|
||||
auto i = _partsCache.upper_bound(
|
||||
KeyFromValues(partial.index, request.offset));
|
||||
if (i == begin(_partsCache)) {
|
||||
return {};
|
||||
}
|
||||
--i;
|
||||
if (ResourceIndexFromKey(i->first) != index) {
|
||||
return {};
|
||||
}
|
||||
const auto alreadyOffset = OffsetFromKey(i->first);
|
||||
const auto alreadyTill = alreadyOffset + i->second.length;
|
||||
if (alreadyTill <= request.offset) {
|
||||
return {};
|
||||
}
|
||||
auto till = alreadyTill;
|
||||
for (auto j = i + 1; j != end(_partsCache); ++j) {
|
||||
const auto offset = OffsetFromKey(j->first);
|
||||
if (ResourceIndexFromKey(j->first) != index || offset > till) {
|
||||
break;
|
||||
}
|
||||
till = offset + j->second.length;
|
||||
if (request.limit <= 0 || till >= request.offset + request.limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const auto length = (request.limit > 0) ? request.limit : (till - request.offset);
|
||||
if (till < request.offset + length) {
|
||||
return {};
|
||||
}
|
||||
auto result = [NSMutableData dataWithLength:length];
|
||||
auto from = request.offset;
|
||||
auto fill = length;
|
||||
auto bytes = static_cast<char*>([result mutableBytes]);
|
||||
for (auto j = i; j != end(_partsCache); ++j) {
|
||||
const auto offset = OffsetFromKey(j->first);
|
||||
const auto copy = std::min(fill, offset + j->second.length - from);
|
||||
Assert(copy > 0);
|
||||
Assert(from >= offset);
|
||||
memcpy(bytes, j->second.bytes.get() + (from - offset), copy);
|
||||
from += copy;
|
||||
fill -= copy;
|
||||
bytes += copy;
|
||||
|
||||
const auto lru = std::find(begin(_partsLRU), end(_partsLRU), j->first);
|
||||
Assert(lru != end(_partsLRU));
|
||||
if (const auto next = lru + 1; next != end(_partsLRU)) {
|
||||
std::rotate(lru, next, end(_partsLRU));
|
||||
}
|
||||
|
||||
if (!fill) {
|
||||
break;
|
||||
}
|
||||
Assert(fill > 0);
|
||||
}
|
||||
Assert(fill == 0);
|
||||
return { .mime = partial.mime, .data = result, .total = partial.total };
|
||||
}
|
||||
|
||||
void Instance::processDataRequest(TaskPointer task, bool started) {
|
||||
if (!started) {
|
||||
_tasks.remove(task);
|
||||
return;
|
||||
}
|
||||
|
||||
@autoreleasepool {
|
||||
|
||||
NSString *url = task.request.URL.absoluteString;
|
||||
NSString *prefix = stdToNS(_dataDomain);
|
||||
if (![url hasPrefix:prefix]) {
|
||||
taskFail(task, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto resourceId = std::string([[url substringFromIndex:[prefix length]] UTF8String]);
|
||||
auto prepared = DataRequest{
|
||||
.id = resourceId,
|
||||
};
|
||||
NSString *rangeHeader = [task.request valueForHTTPHeaderField:@"Range"];
|
||||
if (rangeHeader) {
|
||||
ParseRangeHeaderFor(prepared, std::string([rangeHeader UTF8String]));
|
||||
|
||||
if (const auto cached = fillFromCache(prepared)) {
|
||||
taskDone(task, 0, cached.mime, cached.data, prepared.offset, cached.total);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto index = ++_taskAutoincrement;
|
||||
_tasks[task] = Task{ .index = index, .started = crl::now() };
|
||||
|
||||
const auto requestedOffset = prepared.offset;
|
||||
const auto requestedLimit = prepared.limit;
|
||||
prepared.done = crl::guard(this, [=](DataResponse resolved) {
|
||||
auto &stream = resolved.stream;
|
||||
if (!stream) {
|
||||
return taskFail(task, index);
|
||||
}
|
||||
const auto length = stream->size();
|
||||
Assert(length > 0);
|
||||
|
||||
const auto offset = resolved.streamOffset;
|
||||
if (requestedOffset >= offset + length || offset > requestedOffset) {
|
||||
return taskFail(task, index);
|
||||
}
|
||||
|
||||
auto bytes = std::unique_ptr<char[]>(new char[length]);
|
||||
const auto read = stream->read(bytes.get(), length);
|
||||
Assert(read == length);
|
||||
|
||||
const auto useLength = (requestedLimit > 0)
|
||||
? std::min(requestedLimit, (offset + length - requestedOffset))
|
||||
: (offset + length - requestedOffset);
|
||||
|
||||
const auto useBytes = bytes.get() + (requestedOffset - offset);
|
||||
const auto data = [NSData dataWithBytes:useBytes length:useLength];
|
||||
|
||||
const auto mime = stream->mime();
|
||||
const auto total = resolved.totalSize ? resolved.totalSize : length;
|
||||
const auto i = _partialResources.find(resourceId);
|
||||
if (i != end(_partialResources)) {
|
||||
auto &partial = i->second;
|
||||
if (partial.mime.empty()) {
|
||||
partial.mime = mime;
|
||||
}
|
||||
if (!partial.total) {
|
||||
partial.total = total;
|
||||
}
|
||||
addToCache(partial.index, offset, { std::move(bytes), length });
|
||||
}
|
||||
taskDone(task, index, mime, data, requestedOffset, total);
|
||||
});
|
||||
const auto result = _dataRequestHandler
|
||||
? _dataRequestHandler(prepared)
|
||||
: DataResult::Failed;
|
||||
if (result == DataResult::Failed) {
|
||||
return taskFail(task, index);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::navigate(std::string url) {
|
||||
NSString *string = [NSString stringWithUTF8String:url.c_str()];
|
||||
NSURL *native = [NSURL URLWithString:string];
|
||||
[_webview loadRequest:[NSURLRequest requestWithURL:native]];
|
||||
}
|
||||
|
||||
void Instance::navigateToData(std::string id) {
|
||||
auto full = std::string();
|
||||
full.reserve(_dataDomain.size() + id.size());
|
||||
full.append(_dataDomain);
|
||||
full.append(id);
|
||||
navigate(full);
|
||||
}
|
||||
|
||||
void Instance::reload() {
|
||||
[_webview reload];
|
||||
}
|
||||
|
||||
void Instance::init(std::string js) {
|
||||
NSString *string = [NSString stringWithUTF8String:js.c_str()];
|
||||
WKUserScript *script = [[WKUserScript alloc] initWithSource:string injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
|
||||
[_manager addUserScript:script];
|
||||
}
|
||||
|
||||
void Instance::eval(std::string js) {
|
||||
NSString *string = [NSString stringWithUTF8String:js.c_str()];
|
||||
[_webview evaluateJavaScript:string completionHandler:nil];
|
||||
}
|
||||
|
||||
void Instance::focus() {
|
||||
}
|
||||
|
||||
QWidget *Instance::widget() {
|
||||
return _widget.get();
|
||||
}
|
||||
|
||||
void Instance::refreshNavigationHistoryState() {
|
||||
// Not needed here, there are events.
|
||||
}
|
||||
|
||||
auto Instance::navigationHistoryState()
|
||||
-> rpl::producer<NavigationHistoryState> {
|
||||
return _navigationHistoryState.value();
|
||||
}
|
||||
|
||||
void Instance::setOpaqueBg(QColor opaqueBg) {
|
||||
if (@available(macOS 12.0, *)) {
|
||||
[_webview setValue: @NO forKey: @"drawsBackground"];
|
||||
[_webview setUnderPageBackgroundColor:[NSColor clearColor]];
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Available Availability() {
|
||||
return Available{
|
||||
.customSchemeRequests = true,
|
||||
.customRangeRequests = true,
|
||||
.customReferer = true,
|
||||
};
|
||||
}
|
||||
|
||||
bool SupportsEmbedAfterCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SeparateStorageIdSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<Interface> CreateInstance(Config config) {
|
||||
if (!Supported()) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<Instance>(std::move(config));
|
||||
}
|
||||
|
||||
std::string GenerateStorageToken() {
|
||||
return UuidToToken([NSUUID UUID]);
|
||||
}
|
||||
|
||||
void ClearStorageDataByToken(const std::string &token) {
|
||||
if (@available(macOS 14, *)) {
|
||||
if (!token.empty() && token != LegacyStorageIdToken().toStdString()) {
|
||||
if (NSUUID *uuid = UuidFromToken(token)) {
|
||||
// removeDataStoreForIdentifier crashes without that (if not created first).
|
||||
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
|
||||
[configuration setWebsiteDataStore:[WKWebsiteDataStore dataStoreForIdentifier:uuid]];
|
||||
[configuration release];
|
||||
|
||||
[WKWebsiteDataStore
|
||||
removeDataStoreForIdentifier:uuid
|
||||
completionHandler:^(NSError *error) {}];
|
||||
[uuid release];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Webview
|
||||
81
Telegram/lib_webview/webview/platform/win/webview_win.cpp
Normal file
81
Telegram/lib_webview/webview/platform/win/webview_win.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "webview/platform/win/webview_win.h"
|
||||
|
||||
#include "base/random.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "webview/platform/win/webview_windows_edge_chromium.h"
|
||||
#include "webview/platform/win/webview_windows_edge_html.h"
|
||||
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
namespace Webview {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] bool SystemTooOld() {
|
||||
return !Platform::IsWindows8Point1OrGreater();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
base::unique_qptr<QWindow> MakeFramelessWindow() {
|
||||
auto result = base::make_unique_q<QWindow>();
|
||||
result->setFlag(Qt::FramelessWindowHint);
|
||||
return result;
|
||||
}
|
||||
|
||||
Available Availability() {
|
||||
if (SystemTooOld()) {
|
||||
return Available{
|
||||
.error = Available::Error::OldWindows,
|
||||
.details = "Please update your system to Windows 8.1 or later.",
|
||||
};
|
||||
} else if (EdgeChromium::Supported()) {
|
||||
return Available{
|
||||
.customSchemeRequests = true,
|
||||
.customRangeRequests = true,
|
||||
.customReferer = true,
|
||||
};
|
||||
} else if (EdgeHtml::Supported()) {
|
||||
return Available{};
|
||||
}
|
||||
return Available{
|
||||
.error = Available::Error::NoWebview2,
|
||||
.details = "Please install Microsoft Edge Webview2 Runtime.",
|
||||
};
|
||||
}
|
||||
|
||||
bool SupportsEmbedAfterCreate() {
|
||||
return !SystemTooOld()
|
||||
&& !EdgeChromium::Supported()
|
||||
&& EdgeHtml::Supported();
|
||||
}
|
||||
|
||||
bool SeparateStorageIdSupported() {
|
||||
return !SystemTooOld() && EdgeChromium::Supported();
|
||||
}
|
||||
|
||||
std::unique_ptr<Interface> CreateInstance(Config config) {
|
||||
if (SystemTooOld()) {
|
||||
return nullptr;
|
||||
} else if (auto result = EdgeChromium::CreateInstance(config)) {
|
||||
return result;
|
||||
}
|
||||
return EdgeHtml::CreateInstance(config);
|
||||
}
|
||||
|
||||
std::string GenerateStorageToken() {
|
||||
constexpr auto kSize = 16;
|
||||
auto result = std::string(kSize, ' ');
|
||||
base::RandomFill(result.data(), result.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
void ClearStorageDataByToken(const std::string &token) {
|
||||
}
|
||||
|
||||
} // namespace Webview
|
||||
18
Telegram/lib_webview/webview/platform/win/webview_win.h
Normal file
18
Telegram/lib_webview/webview/platform/win/webview_win.h
Normal file
@@ -0,0 +1,18 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/unique_qptr.h"
|
||||
#include "webview/webview_interface.h"
|
||||
|
||||
class QWindow;
|
||||
|
||||
namespace Webview {
|
||||
|
||||
[[nodiscard]] base::unique_qptr<QWindow> MakeFramelessWindow();
|
||||
|
||||
} // namespace Webview
|
||||
@@ -0,0 +1,119 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "webview/platform/win/webview_windows_data_stream.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace Webview {
|
||||
|
||||
DataStreamCOM::DataStreamCOM(
|
||||
std::unique_ptr<DataStream> wrapped)
|
||||
: _wrapped(std::move(wrapped)) {
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::Read(
|
||||
_Out_writes_bytes_to_(cb, *pcbRead) void *pv,
|
||||
_In_ ULONG cb,
|
||||
_Out_opt_ ULONG *pcbRead) {
|
||||
const auto read = _wrapped->read(pv, cb);
|
||||
if (read < 0) {
|
||||
return E_FAIL;
|
||||
} else if (pcbRead) {
|
||||
*pcbRead = read;
|
||||
}
|
||||
return (read == cb) ? S_OK : S_FALSE;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::Write(
|
||||
_In_reads_bytes_(cb) const void *pv,
|
||||
_In_ ULONG cb,
|
||||
_Out_opt_ ULONG *pcbWritten) {
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::Seek(
|
||||
/* [in] */ LARGE_INTEGER dlibMove,
|
||||
/* [in] */ DWORD dwOrigin,
|
||||
/* [annotation] */
|
||||
_Out_opt_ ULARGE_INTEGER *plibNewPosition) {
|
||||
const auto origin = [&] {
|
||||
switch (dwOrigin) {
|
||||
case STREAM_SEEK_SET: return SEEK_SET;
|
||||
case STREAM_SEEK_CUR: return SEEK_CUR;
|
||||
case STREAM_SEEK_END: return SEEK_END;
|
||||
}
|
||||
return -1;
|
||||
}();
|
||||
if (origin < 0) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
const auto position = _wrapped->seek(origin, dlibMove.QuadPart);
|
||||
if (position < 0) {
|
||||
return E_FAIL;
|
||||
} else if (plibNewPosition) {
|
||||
plibNewPosition->QuadPart = position;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::SetSize(
|
||||
/* [in] */ ULARGE_INTEGER libNewSize) {
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::CopyTo(
|
||||
/* [annotation][unique][in] */
|
||||
_In_ IStream *pstm,
|
||||
/* [in] */ ULARGE_INTEGER cb,
|
||||
/* [annotation] */
|
||||
_Out_opt_ ULARGE_INTEGER *pcbRead,
|
||||
/* [annotation] */
|
||||
_Out_opt_ ULARGE_INTEGER *pcbWritten) {
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::Commit(
|
||||
/* [in] */ DWORD grfCommitFlags) {
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::Revert(void) {
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::LockRegion(
|
||||
/* [in] */ ULARGE_INTEGER libOffset,
|
||||
/* [in] */ ULARGE_INTEGER cb,
|
||||
/* [in] */ DWORD dwLockType) {
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::UnlockRegion(
|
||||
/* [in] */ ULARGE_INTEGER libOffset,
|
||||
/* [in] */ ULARGE_INTEGER cb,
|
||||
/* [in] */ DWORD dwLockType) {
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::Stat(
|
||||
/* [out] */ __RPC__out STATSTG *pstatstg,
|
||||
/* [in] */ DWORD grfStatFlag) {
|
||||
const auto size = _wrapped->size();
|
||||
if (size >= 0) {
|
||||
pstatstg->cbSize.QuadPart = size;
|
||||
return S_OK;
|
||||
}
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DataStreamCOM::Clone(
|
||||
/* [out] */ __RPC__deref_out_opt IStream **ppstm) {
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
} // namespace Webview
|
||||
@@ -0,0 +1,80 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/platform/win/base_windows_wrl.h"
|
||||
#include "base/platform/win/wrl/wrl_implements_h.h"
|
||||
#include "webview/webview_data_stream.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Webview {
|
||||
|
||||
class DataStreamCOM final
|
||||
: public Microsoft::WRL::RuntimeClass<
|
||||
Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
|
||||
IStream> {
|
||||
public:
|
||||
explicit DataStreamCOM(std::unique_ptr<DataStream> wrapped);
|
||||
~DataStreamCOM() = default;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Read(
|
||||
_Out_writes_bytes_to_(cb, *pcbRead) void *pv,
|
||||
_In_ ULONG cb,
|
||||
_Out_opt_ ULONG *pcbRead) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Write(
|
||||
_In_reads_bytes_(cb) const void *pv,
|
||||
_In_ ULONG cb,
|
||||
_Out_opt_ ULONG *pcbWritten) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Seek(
|
||||
/* [in] */ LARGE_INTEGER dlibMove,
|
||||
/* [in] */ DWORD dwOrigin,
|
||||
/* [annotation] */
|
||||
_Out_opt_ ULARGE_INTEGER *plibNewPosition) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE SetSize(
|
||||
/* [in] */ ULARGE_INTEGER libNewSize) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE CopyTo(
|
||||
/* [annotation][unique][in] */
|
||||
_In_ IStream *pstm,
|
||||
/* [in] */ ULARGE_INTEGER cb,
|
||||
/* [annotation] */
|
||||
_Out_opt_ ULARGE_INTEGER *pcbRead,
|
||||
/* [annotation] */
|
||||
_Out_opt_ ULARGE_INTEGER *pcbWritten) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Commit(
|
||||
/* [in] */ DWORD grfCommitFlags) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Revert(void) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE LockRegion(
|
||||
/* [in] */ ULARGE_INTEGER libOffset,
|
||||
/* [in] */ ULARGE_INTEGER cb,
|
||||
/* [in] */ DWORD dwLockType) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE UnlockRegion(
|
||||
/* [in] */ ULARGE_INTEGER libOffset,
|
||||
/* [in] */ ULARGE_INTEGER cb,
|
||||
/* [in] */ DWORD dwLockType) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Stat(
|
||||
/* [out] */ __RPC__out STATSTG *pstatstg,
|
||||
/* [in] */ DWORD grfStatFlag) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Clone(
|
||||
/* [out] */ __RPC__deref_out_opt IStream **ppstm) override;
|
||||
|
||||
private:
|
||||
const std::unique_ptr<DataStream> _wrapped;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Webview
|
||||
@@ -0,0 +1,920 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "webview/platform/win/webview_windows_edge_chromium.h"
|
||||
|
||||
#include "webview/webview_data_stream.h"
|
||||
#include "webview/webview_embed.h"
|
||||
#include "webview/platform/win/webview_windows_data_stream.h"
|
||||
#include "base/algorithm.h"
|
||||
#include "base/basic_types.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "base/flat_map.h"
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/options.h"
|
||||
#include "base/variant.h"
|
||||
#include "base/unique_qptr.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/platform/win/base_windows_co_task_mem.h"
|
||||
#include "base/platform/win/base_windows_winrt.h"
|
||||
#include "base/platform/win/wrl/wrl_implements_h.h"
|
||||
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtGui/QDesktopServices>
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
#include <crl/common/crl_common_on_main_guarded.h>
|
||||
#include <rpl/variable.h>
|
||||
|
||||
#include <string>
|
||||
#include <locale>
|
||||
#include <shlwapi.h>
|
||||
#include <WebView2.h>
|
||||
#include <WebView2EnvironmentOptions.h>
|
||||
|
||||
namespace Webview::EdgeChromium {
|
||||
namespace {
|
||||
|
||||
constexpr auto kDataUrlPrefix
|
||||
= std::string_view("http://desktop-app-resource/");
|
||||
|
||||
[[nodiscard]] std::wstring ToWide(std::string_view string) {
|
||||
const auto length = MultiByteToWideChar(
|
||||
CP_UTF8,
|
||||
0,
|
||||
string.data(),
|
||||
string.size(),
|
||||
nullptr,
|
||||
0);
|
||||
auto result = std::wstring(length, wchar_t{});
|
||||
MultiByteToWideChar(
|
||||
CP_UTF8,
|
||||
0,
|
||||
string.data(),
|
||||
string.size(),
|
||||
result.data(),
|
||||
result.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string FromWide(std::wstring_view string) {
|
||||
const auto length = WideCharToMultiByte(
|
||||
CP_UTF8,
|
||||
0,
|
||||
string.data(),
|
||||
string.size(),
|
||||
nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr);
|
||||
auto result = std::string(length, char{});
|
||||
WideCharToMultiByte(
|
||||
CP_UTF8,
|
||||
0,
|
||||
string.data(),
|
||||
string.size(),
|
||||
result.data(),
|
||||
result.size(),
|
||||
nullptr,
|
||||
nullptr);
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string FromWide(const base::CoTaskMemString &string) {
|
||||
return string ? FromWide(string.data()) : std::string();
|
||||
}
|
||||
|
||||
class Handler
|
||||
: public winrt::implements<
|
||||
Handler,
|
||||
ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler,
|
||||
ICoreWebView2CreateCoreWebView2ControllerCompletedHandler,
|
||||
ICoreWebView2WebMessageReceivedEventHandler,
|
||||
ICoreWebView2PermissionRequestedEventHandler,
|
||||
ICoreWebView2NavigationStartingEventHandler,
|
||||
ICoreWebView2NavigationCompletedEventHandler,
|
||||
ICoreWebView2ContentLoadingEventHandler,
|
||||
ICoreWebView2DocumentTitleChangedEventHandler,
|
||||
ICoreWebView2SourceChangedEventHandler,
|
||||
ICoreWebView2NewWindowRequestedEventHandler,
|
||||
ICoreWebView2ScriptDialogOpeningEventHandler,
|
||||
ICoreWebView2WebResourceRequestedEventHandler,
|
||||
ICoreWebView2ZoomFactorChangedEventHandler>
|
||||
, public ZoomController
|
||||
, public base::has_weak_ptr {
|
||||
|
||||
public:
|
||||
Handler(
|
||||
Config config,
|
||||
HWND handle,
|
||||
std::function<void(not_null<Handler*>)> saveThis,
|
||||
std::function<void()> readyHandler);
|
||||
~Handler();
|
||||
|
||||
const winrt::com_ptr<ICoreWebView2Environment> &environment() const {
|
||||
return _environment;
|
||||
}
|
||||
const winrt::com_ptr<ICoreWebView2Controller> &controller() const {
|
||||
return _controller;
|
||||
}
|
||||
const winrt::com_ptr<ICoreWebView2> &webview() const {
|
||||
return _webview;
|
||||
}
|
||||
[[nodiscard]] bool valid() const {
|
||||
return _window && _environment && _controller && _webview;
|
||||
}
|
||||
|
||||
void setOpaqueBg(QColor opaqueBg);
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
HRESULT res,
|
||||
ICoreWebView2Environment *env) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
HRESULT res,
|
||||
ICoreWebView2Controller *controller) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2WebMessageReceivedEventArgs *args) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2PermissionRequestedEventArgs *args) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2NavigationStartingEventArgs *args) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2NavigationCompletedEventArgs *args) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2ContentLoadingEventArgs *args) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
IUnknown *args) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2SourceChangedEventArgs *args) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2NewWindowRequestedEventArgs *args) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2ScriptDialogOpeningEventArgs *args) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2WebResourceRequestedEventArgs *args) override;
|
||||
HRESULT STDMETHODCALLTYPE Invoke(
|
||||
ICoreWebView2Controller *sender,
|
||||
IUnknown *args) override;
|
||||
|
||||
rpl::producer<int> zoomValue() override {
|
||||
return _zoomValue.value();
|
||||
}
|
||||
void setZoom(int zoom) {
|
||||
if (_controller) {
|
||||
_controller->put_ZoomFactor(zoom / 100.);
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<NavigationHistoryState> navigationHistoryState() {
|
||||
return _navigationHistoryState.value();
|
||||
}
|
||||
|
||||
[[nodiscard]] ZoomController *zoomController() {
|
||||
return this;
|
||||
}
|
||||
|
||||
private:
|
||||
void updateHistoryStates();
|
||||
|
||||
HWND _window = nullptr;
|
||||
winrt::com_ptr<ICoreWebView2Environment> _environment;
|
||||
winrt::com_ptr<ICoreWebView2Controller> _controller;
|
||||
winrt::com_ptr<ICoreWebView2> _webview;
|
||||
std::function<void(std::string)> _messageHandler;
|
||||
std::function<bool(std::string, bool)> _navigationStartHandler;
|
||||
std::function<void(bool)> _navigationDoneHandler;
|
||||
std::function<DialogResult(DialogArgs)> _dialogHandler;
|
||||
std::function<DataResult(DataRequest)> _dataRequestHandler;
|
||||
std::function<void()> _readyHandler;
|
||||
base::flat_map<
|
||||
winrt::com_ptr<ICoreWebView2WebResourceRequestedEventArgs>,
|
||||
winrt::com_ptr<ICoreWebView2Deferral>> _pending;
|
||||
|
||||
rpl::variable<NavigationHistoryState> _navigationHistoryState;
|
||||
rpl::variable<int> _zoomValue;
|
||||
|
||||
QColor _opaqueBg;
|
||||
bool _debug = false;
|
||||
|
||||
};
|
||||
|
||||
Handler::Handler(
|
||||
Config config,
|
||||
HWND handle,
|
||||
std::function<void(not_null<Handler*>)> saveThis,
|
||||
std::function<void()> readyHandler)
|
||||
: _window(handle)
|
||||
, _messageHandler(std::move(config.messageHandler))
|
||||
, _navigationStartHandler(std::move(config.navigationStartHandler))
|
||||
, _navigationDoneHandler(std::move(config.navigationDoneHandler))
|
||||
, _dialogHandler(std::move(config.dialogHandler))
|
||||
, _dataRequestHandler(std::move(config.dataRequestHandler))
|
||||
, _readyHandler(std::move(readyHandler))
|
||||
, _opaqueBg(config.opaqueBg)
|
||||
, _debug(config.debug) {
|
||||
saveThis(this);
|
||||
setOpaqueBg(_opaqueBg);
|
||||
}
|
||||
|
||||
Handler::~Handler() = default;
|
||||
|
||||
void Handler::setOpaqueBg(QColor opaqueBg) {
|
||||
_opaqueBg = Platform::IsWindows10OrGreater()
|
||||
? QColor(255, 255, 255, 0)
|
||||
: opaqueBg;
|
||||
if (const auto late = _controller.try_as<ICoreWebView2Controller2>()) {
|
||||
late->put_DefaultBackgroundColor({
|
||||
uchar(_opaqueBg.alpha()),
|
||||
uchar(_opaqueBg.red()),
|
||||
uchar(_opaqueBg.green()),
|
||||
uchar(_opaqueBg.blue())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
HRESULT res,
|
||||
ICoreWebView2Environment *env) {
|
||||
_environment.copy_from(env);
|
||||
if (!_environment) {
|
||||
return E_FAIL;
|
||||
}
|
||||
_environment->CreateCoreWebView2Controller(_window, this);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
HRESULT res,
|
||||
ICoreWebView2Controller *controller) {
|
||||
if (!_readyHandler) {
|
||||
return S_OK;
|
||||
}
|
||||
_controller.copy_from(controller);
|
||||
const auto guard = gsl::finally([&] {
|
||||
const auto onstack = _readyHandler;
|
||||
_readyHandler = nullptr;
|
||||
onstack();
|
||||
});
|
||||
if (!_controller) {
|
||||
_window = nullptr;
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
_controller->get_CoreWebView2(_webview.put());
|
||||
if (!_webview) {
|
||||
return E_FAIL;
|
||||
}
|
||||
auto token = ::EventRegistrationToken();
|
||||
_webview->add_WebMessageReceived(this, &token);
|
||||
_webview->add_PermissionRequested(this, &token);
|
||||
_webview->add_NavigationStarting(this, &token);
|
||||
_webview->add_NavigationCompleted(this, &token);
|
||||
_webview->add_ContentLoading(this, &token);
|
||||
_webview->add_DocumentTitleChanged(this, &token);
|
||||
_webview->add_SourceChanged(this, &token);
|
||||
_webview->add_NewWindowRequested(this, &token);
|
||||
_webview->add_ScriptDialogOpening(this, &token);
|
||||
_webview->add_WebResourceRequested(this, &token);
|
||||
|
||||
_controller->add_ZoomFactorChanged(this, &token);
|
||||
|
||||
const auto filter = ToWide(kDataUrlPrefix) + L'*';
|
||||
auto hr = _webview->AddWebResourceRequestedFilter(
|
||||
filter.c_str(),
|
||||
COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL);
|
||||
if (hr != S_OK) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
auto settings = winrt::com_ptr<ICoreWebView2Settings>();
|
||||
hr = _webview->get_Settings(settings.put());
|
||||
if (hr != S_OK || !settings) {
|
||||
return E_FAIL;
|
||||
}
|
||||
settings->put_AreDefaultContextMenusEnabled(_debug);
|
||||
settings->put_AreDevToolsEnabled(_debug);
|
||||
settings->put_AreDefaultScriptDialogsEnabled(FALSE);
|
||||
settings->put_IsStatusBarEnabled(FALSE);
|
||||
|
||||
setOpaqueBg(_opaqueBg);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2WebMessageReceivedEventArgs *args) {
|
||||
auto message = base::CoTaskMemString();
|
||||
const auto result = args->TryGetWebMessageAsString(message.put());
|
||||
|
||||
if (result == S_OK && message) {
|
||||
_messageHandler(FromWide(message));
|
||||
sender->PostWebMessageAsString(message.data());
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2PermissionRequestedEventArgs *args) {
|
||||
auto kind = COREWEBVIEW2_PERMISSION_KIND{};
|
||||
const auto result = args->get_PermissionKind(&kind);
|
||||
if (result == S_OK) {
|
||||
if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ) {
|
||||
args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW);
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2NavigationStartingEventArgs *args) {
|
||||
auto uri = base::CoTaskMemString();
|
||||
const auto result = args->get_Uri(uri.put());
|
||||
|
||||
if (result == S_OK && uri) {
|
||||
if (_navigationStartHandler
|
||||
&& !_navigationStartHandler(FromWide(uri), false)) {
|
||||
args->put_Cancel(TRUE);
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
updateHistoryStates();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2NavigationCompletedEventArgs *args) {
|
||||
auto isSuccess = BOOL(FALSE);
|
||||
const auto result = args->get_IsSuccess(&isSuccess);
|
||||
|
||||
if (_navigationDoneHandler) {
|
||||
_navigationDoneHandler(result == S_OK && isSuccess);
|
||||
}
|
||||
updateHistoryStates();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2ContentLoadingEventArgs *args) {
|
||||
updateHistoryStates();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
IUnknown *args) {
|
||||
updateHistoryStates();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2SourceChangedEventArgs *args) {
|
||||
updateHistoryStates();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2NewWindowRequestedEventArgs *args) {
|
||||
auto uri = base::CoTaskMemString();
|
||||
const auto result = args->get_Uri(uri.put());
|
||||
auto isUserInitiated = BOOL{};
|
||||
args->get_IsUserInitiated(&isUserInitiated);
|
||||
args->put_Handled(TRUE);
|
||||
|
||||
if (result == S_OK && uri && isUserInitiated) {
|
||||
const auto url = FromWide(uri);
|
||||
if (_navigationStartHandler && _navigationStartHandler(url, true)) {
|
||||
QDesktopServices::openUrl(QString::fromStdString(url));
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2ScriptDialogOpeningEventArgs *args) {
|
||||
auto kind = COREWEBVIEW2_SCRIPT_DIALOG_KIND_ALERT;
|
||||
auto hr = args->get_Kind(&kind);
|
||||
if (hr != S_OK) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
auto uri = base::CoTaskMemString();
|
||||
auto text = base::CoTaskMemString();
|
||||
auto value = base::CoTaskMemString();
|
||||
hr = args->get_Uri(uri.put());
|
||||
if (hr != S_OK || !uri) {
|
||||
return S_OK;
|
||||
}
|
||||
hr = args->get_Message(text.put());
|
||||
if (hr != S_OK || !text) {
|
||||
return S_OK;
|
||||
}
|
||||
hr = args->get_DefaultText(value.put());
|
||||
if (hr != S_OK || !value) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
const auto type = [&] {
|
||||
switch (kind) {
|
||||
case COREWEBVIEW2_SCRIPT_DIALOG_KIND_ALERT:
|
||||
return DialogType::Alert;
|
||||
case COREWEBVIEW2_SCRIPT_DIALOG_KIND_CONFIRM:
|
||||
return DialogType::Confirm;
|
||||
case COREWEBVIEW2_SCRIPT_DIALOG_KIND_PROMPT:
|
||||
return DialogType::Prompt;
|
||||
}
|
||||
return DialogType::Alert;
|
||||
}();
|
||||
const auto result = _dialogHandler(DialogArgs{
|
||||
.type = type,
|
||||
.value = FromWide(value),
|
||||
.text = FromWide(text),
|
||||
.url = FromWide(uri),
|
||||
});
|
||||
|
||||
if (result.accepted) {
|
||||
args->Accept();
|
||||
if (kind == COREWEBVIEW2_SCRIPT_DIALOG_KIND_PROMPT) {
|
||||
const auto wide = ToWide(result.text);
|
||||
args->put_ResultText(wide.c_str());
|
||||
}
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2Controller *sender,
|
||||
IUnknown *args) {
|
||||
auto zoom = float64(0);
|
||||
sender->get_ZoomFactor(&zoom);
|
||||
_zoomValue = zoom * 100;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Handler::Invoke(
|
||||
ICoreWebView2 *sender,
|
||||
ICoreWebView2WebResourceRequestedEventArgs *args) {
|
||||
auto request = winrt::com_ptr<ICoreWebView2WebResourceRequest>();
|
||||
auto hr = args->get_Request(request.put());
|
||||
if (hr != S_OK || !request) {
|
||||
return S_OK;
|
||||
}
|
||||
auto uri = base::CoTaskMemString();
|
||||
hr = request->get_Uri(uri.put());
|
||||
if (hr != S_OK || !uri) {
|
||||
return S_OK;
|
||||
}
|
||||
auto headers = winrt::com_ptr<ICoreWebView2HttpRequestHeaders>();
|
||||
hr = request->get_Headers(headers.put());
|
||||
if (hr != S_OK || !headers) {
|
||||
return S_OK;
|
||||
}
|
||||
winrt::com_ptr<ICoreWebView2HttpHeadersCollectionIterator> iterator;
|
||||
hr = headers->GetIterator(iterator.put());
|
||||
if (hr != S_OK || !iterator) {
|
||||
return S_OK;
|
||||
}
|
||||
const auto ansi = FromWide(uri);
|
||||
const auto prefix = kDataUrlPrefix.size();
|
||||
if (ansi.size() <= prefix || ansi.compare(0, prefix, kDataUrlPrefix)) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
constexpr auto fail = [](
|
||||
ICoreWebView2Environment *environment,
|
||||
ICoreWebView2WebResourceRequestedEventArgs *args) {
|
||||
auto response = winrt::com_ptr<ICoreWebView2WebResourceResponse>();
|
||||
auto hr = environment->CreateWebResourceResponse(
|
||||
nullptr,
|
||||
404,
|
||||
L"Not Found",
|
||||
L"",
|
||||
response.put());
|
||||
if (hr == S_OK && response) {
|
||||
args->put_Response(response.get());
|
||||
}
|
||||
return S_OK;
|
||||
};
|
||||
constexpr auto done = [](
|
||||
ICoreWebView2Environment *environment,
|
||||
ICoreWebView2WebResourceRequestedEventArgs *args,
|
||||
DataResponse resolved) {
|
||||
auto &stream = resolved.stream;
|
||||
if (!stream) {
|
||||
return fail(environment, args);
|
||||
}
|
||||
const auto length = stream->size();
|
||||
const auto offset = resolved.streamOffset;
|
||||
const auto total = resolved.totalSize ? resolved.totalSize : length;
|
||||
const auto partial = (offset > 0) || (total != length);
|
||||
auto headers = L""
|
||||
L"Content-Type: " + ToWide(stream->mime()) +
|
||||
L"\nAccess-Control-Allow-Origin: *"
|
||||
L"\nAccept-Ranges: bytes"
|
||||
L"\nCache-Control: no-store"
|
||||
L"\nContent-Length: " + std::to_wstring(length);
|
||||
if (partial) {
|
||||
headers += L"\nContent-Range: bytes "
|
||||
+ std::to_wstring(offset)
|
||||
+ L'-'
|
||||
+ std::to_wstring(offset + length - 1)
|
||||
+ L'/'
|
||||
+ std::to_wstring(total);
|
||||
}
|
||||
auto response = winrt::com_ptr<ICoreWebView2WebResourceResponse>();
|
||||
auto hr = environment->CreateWebResourceResponse(
|
||||
Microsoft::WRL::Make<DataStreamCOM>(std::move(stream)).Detach(),
|
||||
partial ? 206 : 200,
|
||||
partial ? L"Partial Content" : L"OK",
|
||||
headers.c_str(),
|
||||
response.put());
|
||||
if (hr == S_OK && response) {
|
||||
args->put_Response(response.get());
|
||||
}
|
||||
return S_OK;
|
||||
};
|
||||
const auto callback = crl::guard(this, [=](DataResponse response) {
|
||||
done(_environment.get(), args, std::move(response));
|
||||
auto copy = winrt::com_ptr<ICoreWebView2WebResourceRequestedEventArgs>();
|
||||
copy.copy_from(args);
|
||||
if (const auto deferral = _pending.take(copy)) {
|
||||
(*deferral)->Complete();
|
||||
}
|
||||
});
|
||||
auto prepared = DataRequest{
|
||||
.id = ansi.substr(prefix),
|
||||
.done = std::move(callback),
|
||||
};
|
||||
while (true) {
|
||||
auto hasCurrent = BOOL();
|
||||
hr = iterator->get_HasCurrentHeader(&hasCurrent);
|
||||
if (hr != S_OK || !hasCurrent) {
|
||||
break;
|
||||
}
|
||||
auto name = base::CoTaskMemString();
|
||||
auto value = base::CoTaskMemString();
|
||||
hr = iterator->GetCurrentHeader(name.put(), value.put());
|
||||
if (hr != S_OK || !name || !value) {
|
||||
break;
|
||||
} else if (FromWide(name) == "Range") {
|
||||
const auto data = FromWide(value);
|
||||
ParseRangeHeaderFor(prepared, FromWide(value));
|
||||
}
|
||||
auto hasNext = BOOL();
|
||||
hr = iterator->MoveNext(&hasNext);
|
||||
if (hr != S_OK || !hasNext) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const auto result = _dataRequestHandler
|
||||
? _dataRequestHandler(prepared)
|
||||
: DataResult::Failed;
|
||||
if (result == DataResult::Failed) {
|
||||
return fail(_environment.get(), args);
|
||||
} else if (result == DataResult::Pending) {
|
||||
auto deferral = winrt::com_ptr<ICoreWebView2Deferral>();
|
||||
hr = args->GetDeferral(deferral.put());
|
||||
if (hr != S_OK || !deferral) {
|
||||
return fail(_environment.get(), args);
|
||||
}
|
||||
auto copy = winrt::com_ptr<ICoreWebView2WebResourceRequestedEventArgs>();
|
||||
copy.copy_from(args);
|
||||
_pending.emplace(std::move(copy), std::move(deferral));
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void Handler::updateHistoryStates() {
|
||||
if (!_webview) {
|
||||
return;
|
||||
};
|
||||
auto canGoBack = BOOL(FALSE);
|
||||
auto canGoForward = BOOL(FALSE);
|
||||
auto url = base::CoTaskMemString();
|
||||
auto title = base::CoTaskMemString();
|
||||
_webview->get_CanGoBack(&canGoBack);
|
||||
_webview->get_CanGoForward(&canGoForward);
|
||||
_webview->get_Source(url.put());
|
||||
_webview->get_DocumentTitle(title.put());
|
||||
_navigationHistoryState = NavigationHistoryState{
|
||||
.url = FromWide(url),
|
||||
.title = FromWide(title),
|
||||
.canGoBack = (canGoBack == TRUE),
|
||||
.canGoForward = (canGoForward == TRUE),
|
||||
};
|
||||
}
|
||||
|
||||
class Instance final : public Interface, public base::has_weak_ptr {
|
||||
public:
|
||||
explicit Instance(Config &&config);
|
||||
~Instance();
|
||||
|
||||
[[nodiscard]] bool failed() const;
|
||||
|
||||
void navigate(std::string url) override;
|
||||
void navigateToData(std::string id) override;
|
||||
void reload() override;
|
||||
|
||||
void init(std::string js) override;
|
||||
void eval(std::string js) override;
|
||||
|
||||
void focus() override;
|
||||
|
||||
QWidget *widget() override;
|
||||
|
||||
void refreshNavigationHistoryState() override;
|
||||
auto navigationHistoryState()
|
||||
-> rpl::producer<NavigationHistoryState> override;
|
||||
|
||||
void setOpaqueBg(QColor opaqueBg) override;
|
||||
|
||||
[[nodiscard]] ZoomController *zoomController() override;
|
||||
|
||||
private:
|
||||
struct NavigateToUrl {
|
||||
std::string url;
|
||||
};
|
||||
struct NavigateToData {
|
||||
std::string id;
|
||||
};
|
||||
struct InitScript {
|
||||
std::string script;
|
||||
};
|
||||
struct EvalScript {
|
||||
std::string script;
|
||||
};
|
||||
|
||||
using ReadyStep = std::variant<
|
||||
NavigateToUrl,
|
||||
NavigateToData,
|
||||
InitScript,
|
||||
EvalScript>;
|
||||
|
||||
void start(Config &&config);
|
||||
[[nodiscard]] bool ready() const;
|
||||
void processReadySteps();
|
||||
void resizeToWindow();
|
||||
|
||||
base::unique_qptr<QWindow> _window;
|
||||
HWND _handle = nullptr;
|
||||
winrt::com_ptr<IUnknown> _ownedHandler;
|
||||
Handler *_handler = nullptr;
|
||||
std::vector<ReadyStep> _waitingForReady;
|
||||
base::unique_qptr<QWidget> _widget;
|
||||
bool _pendingFocus = false;
|
||||
bool _readyFlag = false;
|
||||
|
||||
};
|
||||
|
||||
Instance::Instance(Config &&config)
|
||||
: _window(MakeFramelessWindow())
|
||||
, _handle(HWND(_window->winId()))
|
||||
, _widget(
|
||||
QWidget::createWindowContainer(
|
||||
_window,
|
||||
config.parent,
|
||||
Qt::FramelessWindowHint)) {
|
||||
_widget->show();
|
||||
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
||||
|
||||
init("window.external={invoke:s=>window.chrome.webview.postMessage(s)}");
|
||||
start(std::move(config));
|
||||
}
|
||||
|
||||
Instance::~Instance() {
|
||||
if (_handler) {
|
||||
if (_handler->valid()) {
|
||||
_handler->controller()->Close();
|
||||
}
|
||||
_handler = nullptr;
|
||||
}
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
void Instance::start(Config &&config) {
|
||||
auto options = winrt::com_ptr<ICoreWebView2EnvironmentOptions>(
|
||||
Microsoft::WRL::Make<CoreWebView2EnvironmentOptions>().Detach(),
|
||||
winrt::take_ownership_from_abi);
|
||||
options->put_AdditionalBrowserArguments(
|
||||
L"--disable-features=ElasticOverscroll");
|
||||
|
||||
auto handler = (Handler*)nullptr;
|
||||
const auto ready = [=] {
|
||||
_readyFlag = true;
|
||||
if (_handler) {
|
||||
processReadySteps();
|
||||
}
|
||||
};
|
||||
auto owned = winrt::make<Handler>(
|
||||
config,
|
||||
_handle,
|
||||
[&](not_null<Handler*> that) { handler = that; },
|
||||
crl::guard(this, ready));
|
||||
const auto wpath = ToWide(config.userDataPath);
|
||||
const auto result = CreateCoreWebView2EnvironmentWithOptions(
|
||||
nullptr,
|
||||
wpath.empty() ? nullptr : wpath.c_str(),
|
||||
options.get(),
|
||||
handler);
|
||||
if (result == S_OK) {
|
||||
_ownedHandler = std::move(owned);
|
||||
_handler = handler;
|
||||
if (_readyFlag) {
|
||||
processReadySteps();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Instance::failed() const {
|
||||
return !_handler;
|
||||
}
|
||||
|
||||
void Instance::processReadySteps() {
|
||||
Expects(ready());
|
||||
|
||||
const auto guard = base::make_weak(this);
|
||||
if (!_handler->valid()) {
|
||||
_widget = nullptr;
|
||||
_handle = nullptr;
|
||||
_window = nullptr;
|
||||
_handler = nullptr;
|
||||
base::take(_ownedHandler);
|
||||
return;
|
||||
}
|
||||
if (guard) {
|
||||
_handler->controller()->put_IsVisible(TRUE);
|
||||
}
|
||||
if (const auto widget = guard ? _widget.get() : nullptr) {
|
||||
base::install_event_filter(widget, [=](not_null<QEvent*> e) {
|
||||
if (e->type() == QEvent::Resize || e->type() == QEvent::Move) {
|
||||
InvokeQueued(widget, [=] { resizeToWindow(); });
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
resizeToWindow();
|
||||
}
|
||||
if (guard) {
|
||||
for (const auto &step : base::take(_waitingForReady)) {
|
||||
v::match(step, [&](const NavigateToUrl &data) {
|
||||
navigate(data.url);
|
||||
}, [&](const NavigateToData &data) {
|
||||
navigateToData(data.id);
|
||||
}, [&](const InitScript &data) {
|
||||
init(data.script);
|
||||
}, [&](const EvalScript &data) {
|
||||
eval(data.script);
|
||||
});
|
||||
if (!guard) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (guard && _pendingFocus) {
|
||||
focus();
|
||||
}
|
||||
}
|
||||
|
||||
bool Instance::ready() const {
|
||||
return _handle && _handler && _readyFlag;
|
||||
}
|
||||
|
||||
void Instance::navigate(std::string url) {
|
||||
if (!ready()) {
|
||||
_waitingForReady.push_back(NavigateToUrl{ std::move(url) });
|
||||
return;
|
||||
}
|
||||
const auto wide = ToWide(url);
|
||||
_handler->webview()->Navigate(wide.c_str());
|
||||
}
|
||||
|
||||
void Instance::navigateToData(std::string id) {
|
||||
if (!ready()) {
|
||||
_waitingForReady.push_back(NavigateToData{ std::move(id) });
|
||||
return;
|
||||
}
|
||||
auto full = std::string();
|
||||
full.reserve(kDataUrlPrefix.size() + id.size());
|
||||
full.append(kDataUrlPrefix);
|
||||
full.append(id);
|
||||
navigate(full);
|
||||
}
|
||||
|
||||
void Instance::reload() {
|
||||
if (ready()) {
|
||||
_handler->webview()->Reload();
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::resizeToWindow() {
|
||||
auto bounds = RECT{};
|
||||
GetClientRect(_handle, &bounds);
|
||||
_handler->controller()->put_Bounds(bounds);
|
||||
}
|
||||
|
||||
void Instance::init(std::string js) {
|
||||
if (!ready()) {
|
||||
_waitingForReady.push_back(InitScript{ std::move(js) });
|
||||
return;
|
||||
}
|
||||
const auto wide = ToWide(js);
|
||||
_handler->webview()->AddScriptToExecuteOnDocumentCreated(
|
||||
wide.c_str(),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void Instance::eval(std::string js) {
|
||||
if (!ready()) {
|
||||
_waitingForReady.push_back(EvalScript{ std::move(js) });
|
||||
return;
|
||||
}
|
||||
const auto wide = ToWide(js);
|
||||
_handler->webview()->ExecuteScript(wide.c_str(), nullptr);
|
||||
}
|
||||
|
||||
void Instance::focus() {
|
||||
if (_window) {
|
||||
_window->requestActivate();
|
||||
}
|
||||
if (_handle) {
|
||||
SetForegroundWindow(_handle);
|
||||
SetFocus(_handle);
|
||||
}
|
||||
if (!ready()) {
|
||||
_pendingFocus = true;
|
||||
return;
|
||||
}
|
||||
_handler->controller()->MoveFocus(
|
||||
COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC);
|
||||
}
|
||||
|
||||
QWidget *Instance::widget() {
|
||||
return _widget.get();
|
||||
}
|
||||
|
||||
void Instance::refreshNavigationHistoryState() {
|
||||
// Not needed here, there are events.
|
||||
}
|
||||
|
||||
auto Instance::navigationHistoryState()
|
||||
-> rpl::producer<NavigationHistoryState> {
|
||||
return _handler
|
||||
? _handler->navigationHistoryState()
|
||||
: rpl::single(NavigationHistoryState());
|
||||
}
|
||||
|
||||
ZoomController *Instance::zoomController() {
|
||||
return _handler->zoomController();
|
||||
}
|
||||
|
||||
void Instance::setOpaqueBg(QColor opaqueBg) {
|
||||
if (_handler) {
|
||||
_handler->setOpaqueBg(opaqueBg);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Supported() {
|
||||
if (base::options::value<bool>(kOptionWebviewLegacyEdge)) {
|
||||
return false;
|
||||
}
|
||||
auto version = LPWSTR(nullptr);
|
||||
const auto result = GetAvailableCoreWebView2BrowserVersionString(
|
||||
nullptr,
|
||||
&version);
|
||||
return (result == S_OK);
|
||||
}
|
||||
|
||||
std::unique_ptr<Interface> CreateInstance(Config config) {
|
||||
if (!Supported()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto result = std::make_unique<Instance>(std::move(config));
|
||||
return result->failed() ? nullptr : std::move(result);
|
||||
}
|
||||
|
||||
} // namespace Webview::EdgeChromium
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "webview/platform/win/webview_win.h"
|
||||
|
||||
namespace Webview::EdgeChromium {
|
||||
|
||||
[[nodiscard]] bool Supported();
|
||||
[[nodiscard]] std::unique_ptr<Interface> CreateInstance(Config config);
|
||||
|
||||
} // namespace Webview::EdgeChromium
|
||||
@@ -0,0 +1,384 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "webview/platform/win/webview_windows_edge_html.h"
|
||||
|
||||
#include "base/platform/win/base_windows_winrt.h"
|
||||
#include "base/algorithm.h"
|
||||
#include "base/variant.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "base/unique_qptr.h"
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtGui/QDesktopServices>
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
#include <crl/crl_on_main.h>
|
||||
#include <rpl/variable.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <objbase.h>
|
||||
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Web.UI.Interop.h>
|
||||
|
||||
namespace Webview::EdgeHtml {
|
||||
namespace {
|
||||
|
||||
using namespace winrt;
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::Web::UI;
|
||||
using namespace Windows::Web::UI::Interop;
|
||||
using namespace base::WinRT;
|
||||
|
||||
class Instance final : public Interface, public base::has_weak_ptr {
|
||||
public:
|
||||
explicit Instance(Config config);
|
||||
~Instance();
|
||||
|
||||
void navigate(std::string url) override;
|
||||
void navigateToData(std::string id) override;
|
||||
void reload() override;
|
||||
|
||||
void init(std::string js) override;
|
||||
void eval(std::string js) override;
|
||||
|
||||
void focus() override;
|
||||
|
||||
QWidget *widget() override;
|
||||
|
||||
void refreshNavigationHistoryState() override;
|
||||
auto navigationHistoryState()
|
||||
-> rpl::producer<NavigationHistoryState> override;
|
||||
|
||||
void setOpaqueBg(QColor opaqueBg) override;
|
||||
|
||||
private:
|
||||
struct NavigateToUrl {
|
||||
std::string url;
|
||||
};
|
||||
struct EvalScript {
|
||||
std::string script;
|
||||
};
|
||||
struct SetOpaqueBg {
|
||||
QColor color;
|
||||
};
|
||||
|
||||
using ReadyStep = std::variant<
|
||||
NavigateToUrl,
|
||||
EvalScript,
|
||||
SetOpaqueBg>;
|
||||
|
||||
[[nodiscard]] bool ready() const;
|
||||
void ready(WebViewControl webview);
|
||||
void processReadySteps();
|
||||
void updateHistoryStates();
|
||||
|
||||
Config _config;
|
||||
base::unique_qptr<QWindow> _window;
|
||||
HWND _handle = nullptr;
|
||||
WebViewControlProcess _process;
|
||||
WebViewControl _webview = nullptr;
|
||||
base::unique_qptr<Ui::RpWidget> _widget;
|
||||
QPointer<QWidget> _embed;
|
||||
std::string _initScript;
|
||||
|
||||
rpl::variable<NavigationHistoryState> _navigationHistoryState;
|
||||
|
||||
std::vector<ReadyStep> _waitingForReady;
|
||||
bool _pendingFocus = false;
|
||||
bool _readyFlag = false;
|
||||
|
||||
};
|
||||
|
||||
Instance::Instance(Config config)
|
||||
: _config(std::move(config))
|
||||
, _window(MakeFramelessWindow())
|
||||
, _handle(HWND(_window->winId()))
|
||||
, _widget(base::make_unique_q<Ui::RpWidget>(config.parent)) {
|
||||
_widget->show();
|
||||
|
||||
setOpaqueBg(_config.opaqueBg);
|
||||
init("window.external.invoke = s => window.external.notify(s)");
|
||||
|
||||
init_apartment(apartment_type::single_threaded);
|
||||
const auto weak = base::make_weak(this);
|
||||
_process.CreateWebViewControlAsync(
|
||||
reinterpret_cast<int64_t>(_handle),
|
||||
Rect()
|
||||
).Completed([=](
|
||||
IAsyncOperation<WebViewControl> that,
|
||||
AsyncStatus status) {
|
||||
using namespace base::WinRT;
|
||||
const auto ok = (status == AsyncStatus::Completed) && Try([&] {
|
||||
crl::on_main([=, webview = that.GetResults()]() mutable {
|
||||
if (const auto that = weak.get()) {
|
||||
that->ready(std::move(webview));
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!ok) {
|
||||
crl::on_main([=] {
|
||||
if (const auto that = weak.get()) {
|
||||
that->_widget = nullptr;
|
||||
that->_handle = nullptr;
|
||||
that->_window = nullptr;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_process.ProcessExited([=](const auto &sender, const auto &args) {
|
||||
_webview = nullptr;
|
||||
crl::on_main([=] {
|
||||
if (const auto that = weak.get()) {
|
||||
that->_embed = nullptr;
|
||||
that->_widget = nullptr;
|
||||
that->_handle = nullptr;
|
||||
that->_window = nullptr;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Instance::~Instance() {
|
||||
if (ready()) {
|
||||
base::WinRT::Try([&] {
|
||||
std::exchange(_webview, WebViewControl(nullptr)).Close();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::ready(WebViewControl webview) {
|
||||
auto guard = base::make_weak(this);
|
||||
_webview = std::move(webview);
|
||||
|
||||
_webview.ScriptNotify([handler = _config.messageHandler](
|
||||
const auto &sender,
|
||||
const WebViewControlScriptNotifyEventArgs &args) {
|
||||
if (handler) {
|
||||
handler(winrt::to_string(args.Value()));
|
||||
}
|
||||
});
|
||||
_webview.NavigationStarting([=, handler = _config.navigationStartHandler](
|
||||
const auto &sender,
|
||||
const WebViewControlNavigationStartingEventArgs &args) {
|
||||
if (handler
|
||||
&& !handler(winrt::to_string(args.Uri().AbsoluteUri()), false)) {
|
||||
args.Cancel(true);
|
||||
} else {
|
||||
_webview.AddInitializeScript(winrt::to_hstring(_initScript));
|
||||
}
|
||||
updateHistoryStates();
|
||||
});
|
||||
_webview.ContentLoading([=](const auto &sender, const auto &args) {
|
||||
updateHistoryStates();
|
||||
});
|
||||
_webview.DOMContentLoaded([=](const auto &sender, const auto &args) {
|
||||
updateHistoryStates();
|
||||
});
|
||||
_webview.NavigationCompleted([=, handler = _config.navigationDoneHandler](
|
||||
const auto &sender,
|
||||
const WebViewControlNavigationCompletedEventArgs &args) {
|
||||
if (handler) {
|
||||
handler(args.IsSuccess());
|
||||
}
|
||||
updateHistoryStates();
|
||||
});
|
||||
_webview.NewWindowRequested([=, handler = _config.navigationStartHandler](
|
||||
const auto &sender,
|
||||
const WebViewControlNewWindowRequestedEventArgs &args) {
|
||||
const auto url = winrt::to_string(args.Uri().AbsoluteUri());
|
||||
if (handler && handler(url, true)) {
|
||||
QDesktopServices::openUrl(QString::fromStdString(url));
|
||||
}
|
||||
});
|
||||
|
||||
_embed = QWidget::createWindowContainer(
|
||||
_window,
|
||||
_widget.get(),
|
||||
Qt::FramelessWindowHint);
|
||||
_embed->show();
|
||||
|
||||
_readyFlag = true;
|
||||
processReadySteps();
|
||||
}
|
||||
|
||||
void Instance::updateHistoryStates() {
|
||||
base::WinRT::Try([&] {
|
||||
_navigationHistoryState = NavigationHistoryState{
|
||||
.url = winrt::to_string(_webview.Source().AbsoluteUri()),
|
||||
.title = winrt::to_string(_webview.DocumentTitle()),
|
||||
.canGoBack = _webview.CanGoBack(),
|
||||
.canGoForward = _webview.CanGoForward(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
bool Instance::ready() const {
|
||||
return _handle && _webview && _readyFlag;
|
||||
}
|
||||
|
||||
void Instance::processReadySteps() {
|
||||
Expects(ready());
|
||||
|
||||
const auto guard = base::make_weak(this);
|
||||
if (!_webview) {
|
||||
_widget = nullptr;
|
||||
_handle = nullptr;
|
||||
_window = nullptr;
|
||||
return;
|
||||
}
|
||||
if (guard) {
|
||||
base::WinRT::Try([&] {
|
||||
_webview.Settings().IsScriptNotifyAllowed(true);
|
||||
_webview.IsVisible(true);
|
||||
});
|
||||
}
|
||||
if (guard) {
|
||||
_widget->sizeValue() | rpl::on_next([=](QSize size) {
|
||||
_embed->setGeometry(QRect(QPoint(), size));
|
||||
|
||||
if (!ready()) {
|
||||
return;
|
||||
}
|
||||
RECT r;
|
||||
GetClientRect(_handle, &r);
|
||||
Rect bounds(r.left, r.top, r.right - r.left, r.bottom - r.top);
|
||||
base::WinRT::Try([&] {
|
||||
_webview.Bounds(bounds);
|
||||
});
|
||||
}, _widget->lifetime());
|
||||
}
|
||||
if (guard) {
|
||||
for (const auto &step : base::take(_waitingForReady)) {
|
||||
v::match(step, [&](const NavigateToUrl &data) {
|
||||
navigate(data.url);
|
||||
}, [&](const EvalScript &data) {
|
||||
eval(data.script);
|
||||
}, [&](const SetOpaqueBg &data) {
|
||||
setOpaqueBg(data.color);
|
||||
});
|
||||
if (!guard) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (guard && _pendingFocus) {
|
||||
focus();
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::navigate(std::string url) {
|
||||
if (!ready()) {
|
||||
_waitingForReady.push_back(NavigateToUrl{ std::move(url) });
|
||||
return;
|
||||
}
|
||||
base::WinRT::Try([&] {
|
||||
_webview.Navigate(Uri(winrt::to_hstring(url)));
|
||||
});
|
||||
}
|
||||
|
||||
void Instance::navigateToData(std::string id) {
|
||||
Unexpected("EdgeHtml::Instance::navigateToData.");
|
||||
}
|
||||
|
||||
void Instance::reload() {
|
||||
if (!ready()) {
|
||||
return;
|
||||
}
|
||||
base::WinRT::Try([&] {
|
||||
_webview.Refresh();
|
||||
});
|
||||
}
|
||||
|
||||
void Instance::init(std::string js) {
|
||||
_initScript = _initScript + "(function(){" + js + "})();";
|
||||
}
|
||||
|
||||
void Instance::eval(std::string js) {
|
||||
if (!ready()) {
|
||||
_waitingForReady.push_back(EvalScript{ std::move(js) });
|
||||
return;
|
||||
}
|
||||
base::WinRT::Try([&] {
|
||||
_webview.InvokeScriptAsync(
|
||||
L"eval",
|
||||
single_threaded_vector<hstring>({ winrt::to_hstring(js) }));
|
||||
_webview.InvokeScriptAsync(
|
||||
L"eval",
|
||||
single_threaded_vector<hstring>({ winrt::to_hstring(
|
||||
"document.body.style.backgroundColor='transparent';") }));
|
||||
_webview.InvokeScriptAsync(
|
||||
L"eval",
|
||||
single_threaded_vector<hstring>({ winrt::to_hstring(
|
||||
"document.getElementsByTagName('html')[0].style.backgroundColor='transparent';") }));
|
||||
});
|
||||
}
|
||||
|
||||
void Instance::focus() {
|
||||
if (_window) {
|
||||
_window->requestActivate();
|
||||
}
|
||||
if (_handle) {
|
||||
SetForegroundWindow(_handle);
|
||||
SetFocus(_handle);
|
||||
}
|
||||
if (!ready()) {
|
||||
_pendingFocus = true;
|
||||
return;
|
||||
}
|
||||
base::WinRT::Try([&] {
|
||||
_webview.MoveFocus(WebViewControlMoveFocusReason::Programmatic);
|
||||
});
|
||||
}
|
||||
|
||||
QWidget *Instance::widget() {
|
||||
return _widget.get();
|
||||
}
|
||||
|
||||
void Instance::refreshNavigationHistoryState() {
|
||||
updateHistoryStates();
|
||||
}
|
||||
|
||||
auto Instance::navigationHistoryState()
|
||||
-> rpl::producer<NavigationHistoryState> {
|
||||
return _navigationHistoryState.value();
|
||||
}
|
||||
|
||||
void Instance::setOpaqueBg(QColor opaqueBg) {
|
||||
if (!ready()) {
|
||||
_waitingForReady.push_back(SetOpaqueBg{ opaqueBg });
|
||||
return;
|
||||
}
|
||||
base::WinRT::Try([&] {
|
||||
_webview.DefaultBackgroundColor({
|
||||
uchar(opaqueBg.alpha()),
|
||||
uchar(opaqueBg.red()),
|
||||
uchar(opaqueBg.green()),
|
||||
uchar(opaqueBg.blue())
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Supported() {
|
||||
return Try([&] {
|
||||
return (WebViewControlProcess() != nullptr);
|
||||
}).value_or(false);
|
||||
}
|
||||
|
||||
std::unique_ptr<Interface> CreateInstance(Config config) {
|
||||
return Try([&]() -> std::unique_ptr<Interface> {
|
||||
return std::make_unique<Instance>(std::move(config));
|
||||
}).value_or(nullptr);
|
||||
}
|
||||
|
||||
} // namespace Webview::EdgeHtml
|
||||
@@ -0,0 +1,16 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "webview/platform/win/webview_win.h"
|
||||
|
||||
namespace Webview::EdgeHtml {
|
||||
|
||||
[[nodiscard]] bool Supported();
|
||||
[[nodiscard]] std::unique_ptr<Interface> CreateInstance(Config config);
|
||||
|
||||
} // namespace Webview::EdgeHtml
|
||||
40
Telegram/lib_webview/webview/webview_common.h
Normal file
40
Telegram/lib_webview/webview/webview_common.h
Normal file
@@ -0,0 +1,40 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/basic_types.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QColor>
|
||||
|
||||
namespace Webview {
|
||||
|
||||
struct StorageId {
|
||||
QString path;
|
||||
QByteArray token;
|
||||
|
||||
explicit operator bool() const {
|
||||
return !path.isEmpty() && !token.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] inline QByteArray LegacyStorageIdToken() {
|
||||
return "<legacy>"_q;
|
||||
}
|
||||
|
||||
struct ThemeParams {
|
||||
QColor bodyBg;
|
||||
QColor titleBg;
|
||||
QColor scrollBg;
|
||||
QColor scrollBgOver;
|
||||
QColor scrollBarBg;
|
||||
QColor scrollBarBgOver;
|
||||
QByteArray json;
|
||||
};
|
||||
|
||||
} // namespace Webview
|
||||
25
Telegram/lib_webview/webview/webview_data_stream.h
Normal file
25
Telegram/lib_webview/webview/webview_data_stream.h
Normal file
@@ -0,0 +1,25 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <cinttypes>
|
||||
#include <string>
|
||||
|
||||
namespace Webview {
|
||||
|
||||
class DataStream {
|
||||
public:
|
||||
virtual ~DataStream() = default;
|
||||
|
||||
[[nodiscard]] virtual std::int64_t size() = 0;
|
||||
[[nodiscard]] virtual std::string mime() = 0;
|
||||
|
||||
virtual std::int64_t seek(int origin, std::int64_t position) = 0;
|
||||
virtual std::int64_t read(void *buffer, std::int64_t requested) = 0;
|
||||
};
|
||||
|
||||
} // namespace Webview
|
||||
61
Telegram/lib_webview/webview/webview_data_stream_memory.cpp
Normal file
61
Telegram/lib_webview/webview/webview_data_stream_memory.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "webview/webview_data_stream_memory.h"
|
||||
|
||||
namespace Webview {
|
||||
|
||||
DataStreamFromMemory::DataStreamFromMemory(
|
||||
QByteArray data,
|
||||
std::string mime)
|
||||
: _data(data)
|
||||
, _mime(mime) {
|
||||
}
|
||||
|
||||
std::int64_t DataStreamFromMemory::size() {
|
||||
return _data.size();
|
||||
}
|
||||
|
||||
std::string DataStreamFromMemory::mime() {
|
||||
return _mime;
|
||||
}
|
||||
|
||||
std::int64_t DataStreamFromMemory::seek(
|
||||
int origin,
|
||||
std::int64_t position) {
|
||||
const auto length = size();
|
||||
switch (origin) {
|
||||
case SEEK_SET:
|
||||
return (position >= 0 && position <= length)
|
||||
? ((_offset = position))
|
||||
: -1;
|
||||
case SEEK_CUR:
|
||||
return (_offset + position >= 0 && _offset + position <= length)
|
||||
? ((_offset += position))
|
||||
: -1;
|
||||
case SEEK_END:
|
||||
return (length + position >= 0 && position <= 0)
|
||||
? ((_offset = length + position))
|
||||
: -1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::int64_t DataStreamFromMemory::read(
|
||||
void *buffer,
|
||||
std::int64_t requested) {
|
||||
if (requested < 0) {
|
||||
return -1;
|
||||
}
|
||||
const auto copy = std::min(std::int64_t(size() - _offset), requested);
|
||||
if (copy > 0) {
|
||||
memcpy(buffer, _data.constData() + _offset, copy);
|
||||
_offset += copy;
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
} // namespace Webview
|
||||
37
Telegram/lib_webview/webview/webview_data_stream_memory.h
Normal file
37
Telegram/lib_webview/webview/webview_data_stream_memory.h
Normal file
@@ -0,0 +1,37 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/basic_types.h"
|
||||
#include "webview/webview_data_stream.h"
|
||||
|
||||
#include <QtCore/QByteArray>
|
||||
|
||||
namespace Webview {
|
||||
|
||||
class DataStreamFromMemory final : public DataStream {
|
||||
public:
|
||||
DataStreamFromMemory(QByteArray data, std::string mime);
|
||||
|
||||
[[nodiscard]] std::int64_t size() override;
|
||||
[[nodiscard]] std::string mime() override;
|
||||
|
||||
std::int64_t seek(int origin, std::int64_t position) override;
|
||||
std::int64_t read(void *buffer, std::int64_t requested) override;
|
||||
|
||||
[[nodiscard]] const char *bytes() const {
|
||||
return _data.data();
|
||||
}
|
||||
|
||||
private:
|
||||
QByteArray _data;
|
||||
std::string _mime;
|
||||
int64 _offset = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Webview
|
||||
254
Telegram/lib_webview/webview/webview_dialog.cpp
Normal file
254
Telegram/lib_webview/webview/webview_dialog.cpp
Normal file
@@ -0,0 +1,254 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "webview/webview_dialog.h"
|
||||
|
||||
#include "webview/webview_interface.h"
|
||||
#include "ui/widgets/separate_panel.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "ui/integration.h"
|
||||
#include "ui/qt_object_factory.h"
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/unique_qptr.h"
|
||||
#include "base/integration.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/style_layers.h"
|
||||
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QEventLoop>
|
||||
|
||||
namespace Webview {
|
||||
namespace {
|
||||
|
||||
constexpr auto kPopupsQuicklyLimit = 3;
|
||||
constexpr auto kPopupsQuicklyDelay = 8 * crl::time(1000);
|
||||
|
||||
bool InBlockingPopup/* = false*/;
|
||||
int PopupsShownQuickly/* = 0*/;
|
||||
crl::time PopupLastShown/* = 0*/;
|
||||
|
||||
} // namespace
|
||||
|
||||
PopupResult ShowBlockingPopup(PopupArgs &&args) {
|
||||
if (InBlockingPopup) {
|
||||
return {};
|
||||
}
|
||||
InBlockingPopup = true;
|
||||
const auto guard = gsl::finally([] {
|
||||
InBlockingPopup = false;
|
||||
});
|
||||
|
||||
if (!args.ignoreFloodCheck) {
|
||||
const auto now = crl::now();
|
||||
if (!PopupLastShown || PopupLastShown + kPopupsQuicklyDelay <= now) {
|
||||
PopupsShownQuickly = 1;
|
||||
} else if (++PopupsShownQuickly > kPopupsQuicklyLimit) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
const auto timeguard = gsl::finally([] {
|
||||
PopupLastShown = crl::now();
|
||||
});
|
||||
|
||||
// This fixes animations in a nested event loop.
|
||||
base::Integration::Instance().enterFromEventLoop([] {});
|
||||
|
||||
auto result = PopupResult();
|
||||
auto context = QObject();
|
||||
|
||||
QEventLoop loop;
|
||||
auto running = true;
|
||||
auto widget = base::unique_qptr<Ui::SeparatePanel>();
|
||||
InvokeQueued(&context, [&] {
|
||||
widget = base::make_unique_q<Ui::SeparatePanel>(Ui::SeparatePanelArgs{
|
||||
.parent = args.parent,
|
||||
});
|
||||
const auto raw = widget.get();
|
||||
|
||||
raw->setWindowFlag(Qt::WindowStaysOnTopHint, false);
|
||||
raw->setAttribute(Qt::WA_DeleteOnClose, false);
|
||||
raw->setAttribute(Qt::WA_ShowModal, true);
|
||||
|
||||
const auto titleHeight = args.title.isEmpty()
|
||||
? st::separatePanelNoTitleHeight
|
||||
: st::separatePanelTitleHeight;
|
||||
if (!args.title.isEmpty()) {
|
||||
raw->setTitle(rpl::single(args.title));
|
||||
}
|
||||
raw->setTitleHeight(titleHeight);
|
||||
auto layout = base::make_unique_q<Ui::VerticalLayout>(raw);
|
||||
const auto skip = st::boxDividerHeight;
|
||||
const auto container = layout.get();
|
||||
const auto addedRightPadding = args.title.isEmpty()
|
||||
? (st::separatePanelClose.width - st::boxRowPadding.right())
|
||||
: 0;
|
||||
const auto label = container->add(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
container,
|
||||
rpl::single(args.text),
|
||||
st::boxLabel),
|
||||
st::boxRowPadding + QMargins(0, 0, addedRightPadding, 0));
|
||||
label->resizeToWidth(st::boxWideWidth
|
||||
- st::boxRowPadding.left()
|
||||
- st::boxRowPadding.right()
|
||||
- addedRightPadding);
|
||||
const auto input = args.value
|
||||
? container->add(
|
||||
object_ptr<Ui::InputField>(
|
||||
container,
|
||||
st::defaultInputField,
|
||||
rpl::single(QString()),
|
||||
*args.value),
|
||||
st::boxRowPadding + QMargins(0, 0, 0, skip))
|
||||
: nullptr;
|
||||
const auto buttonPadding = st::webviewDialogPadding;
|
||||
const auto buttons = container->add(
|
||||
object_ptr<Ui::RpWidget>(container),
|
||||
QMargins(
|
||||
buttonPadding.left(),
|
||||
buttonPadding.top(),
|
||||
buttonPadding.left(),
|
||||
buttonPadding.bottom()));
|
||||
const auto list = buttons->lifetime().make_state<
|
||||
std::vector<not_null<Ui::RoundButton*>>
|
||||
>();
|
||||
list->reserve(args.buttons.size());
|
||||
for (const auto &descriptor : args.buttons) {
|
||||
using Type = PopupArgs::Button::Type;
|
||||
const auto text = [&] {
|
||||
const auto integration = &Ui::Integration::Instance();
|
||||
switch (descriptor.type) {
|
||||
case Type::Default: return descriptor.text;
|
||||
case Type::Ok: return integration->phraseButtonOk();
|
||||
case Type::Close: return integration->phraseButtonClose();
|
||||
case Type::Cancel: return integration->phraseButtonCancel();
|
||||
case Type::Destructive: return descriptor.text;
|
||||
}
|
||||
Unexpected("Button type in blocking popup.");
|
||||
}();
|
||||
const auto button = Ui::CreateChild<Ui::RoundButton>(
|
||||
buttons,
|
||||
rpl::single(text),
|
||||
(descriptor.type != Type::Destructive
|
||||
? st::webviewDialogButton
|
||||
: st::webviewDialogDestructiveButton));
|
||||
button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
||||
button->setClickedCallback([=, &result, id = descriptor.id]{
|
||||
result.id = id;
|
||||
if (input) {
|
||||
result.value = input->getLastText();
|
||||
}
|
||||
raw->hideGetDuration();
|
||||
});
|
||||
list->push_back(button);
|
||||
}
|
||||
|
||||
buttons->resizeToWidth(st::boxWideWidth - 2 * buttonPadding.left());
|
||||
buttons->widthValue(
|
||||
) | rpl::on_next([=](int width) {
|
||||
const auto count = list->size();
|
||||
const auto skip = st::webviewDialogPadding.right();
|
||||
auto buttonsWidth = 0;
|
||||
for (const auto &button : *list) {
|
||||
buttonsWidth += button->width() + (buttonsWidth ? skip : 0);
|
||||
}
|
||||
const auto vertical = (count > 1) && (buttonsWidth > width);
|
||||
const auto single = st::webviewDialogSubmit.height;
|
||||
auto top = 0;
|
||||
auto right = 0;
|
||||
for (const auto &button : *list) {
|
||||
button->moveToRight(right, top, width);
|
||||
if (vertical) {
|
||||
top += single + skip;
|
||||
} else {
|
||||
right += button->width() + skip;
|
||||
}
|
||||
}
|
||||
const auto height = (top > 0) ? (top - skip) : single;
|
||||
if (buttons->height() != height) {
|
||||
buttons->resize(buttons->width(), height);
|
||||
}
|
||||
}, buttons->lifetime());
|
||||
|
||||
container->resizeToWidth(st::boxWideWidth);
|
||||
|
||||
container->heightValue(
|
||||
) | rpl::on_next([=](int height) {
|
||||
raw->setInnerSize({ st::boxWideWidth, titleHeight + height });
|
||||
}, container->lifetime());
|
||||
|
||||
if (input) {
|
||||
input->selectAll();
|
||||
input->setFocusFast();
|
||||
const auto submitted = [=, &result] {
|
||||
result.value = input->getLastText();
|
||||
raw->hideGetDuration();
|
||||
};
|
||||
input->submits(
|
||||
) | rpl::on_next(submitted, input->lifetime());
|
||||
}
|
||||
container->events(
|
||||
) | rpl::on_next([=](not_null<QEvent*> event) {
|
||||
if (input && event->type() == QEvent::FocusIn) {
|
||||
input->setFocus();
|
||||
}
|
||||
}, container->lifetime());
|
||||
|
||||
raw->closeRequests() | rpl::on_next([=] {
|
||||
raw->hideGetDuration();
|
||||
}, raw->lifetime());
|
||||
|
||||
const auto finish = [&] {
|
||||
if (running) {
|
||||
running = false;
|
||||
loop.quit();
|
||||
}
|
||||
};
|
||||
QObject::connect(raw, &QObject::destroyed, finish);
|
||||
raw->closeEvents() | rpl::on_next(finish, raw->lifetime());
|
||||
|
||||
raw->showInner(std::move(layout));
|
||||
});
|
||||
loop.exec(QEventLoop::DialogExec);
|
||||
widget = nullptr;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DialogResult DefaultDialogHandler(DialogArgs &&args) {
|
||||
auto buttons = std::vector<PopupArgs::Button>();
|
||||
buttons.push_back({
|
||||
.id = "ok",
|
||||
.type = PopupArgs::Button::Type::Ok,
|
||||
});
|
||||
if (args.type != DialogType::Alert) {
|
||||
buttons.push_back({
|
||||
.id = "cancel",
|
||||
.type = PopupArgs::Button::Type::Cancel,
|
||||
});
|
||||
}
|
||||
const auto result = ShowBlockingPopup({
|
||||
.parent = args.parent,
|
||||
.title = QUrl(QString::fromStdString(args.url)).host(),
|
||||
.text = QString::fromStdString(args.text),
|
||||
.value = (args.type == DialogType::Prompt
|
||||
? QString::fromStdString(args.value)
|
||||
: std::optional<QString>()),
|
||||
.buttons = std::move(buttons),
|
||||
});
|
||||
return {
|
||||
.text = (result.id == "cancel"
|
||||
? std::string()
|
||||
: result.value.value_or(QString()).toStdString()),
|
||||
.accepted = (result.id == "ok" || result.value.has_value()),
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Webview
|
||||
50
Telegram/lib_webview/webview/webview_dialog.h
Normal file
50
Telegram/lib_webview/webview/webview_dialog.h
Normal file
@@ -0,0 +1,50 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QString>
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
class QWidget;
|
||||
|
||||
namespace Webview {
|
||||
|
||||
struct PopupArgs {
|
||||
struct Button {
|
||||
enum class Type {
|
||||
Default,
|
||||
Ok,
|
||||
Close,
|
||||
Cancel,
|
||||
Destructive,
|
||||
};
|
||||
QString id;
|
||||
QString text;
|
||||
Type type = Type::Default;
|
||||
};
|
||||
|
||||
QWidget *parent = nullptr;
|
||||
QString title;
|
||||
QString text;
|
||||
std::optional<QString> value;
|
||||
std::vector<Button> buttons;
|
||||
bool ignoreFloodCheck = false;
|
||||
};
|
||||
struct PopupResult {
|
||||
std::optional<QString> id;
|
||||
std::optional<QString> value;
|
||||
};
|
||||
[[nodiscard]] PopupResult ShowBlockingPopup(PopupArgs &&args);
|
||||
|
||||
struct DialogArgs;
|
||||
struct DialogResult;
|
||||
|
||||
[[nodiscard]] DialogResult DefaultDialogHandler(DialogArgs &&args);
|
||||
|
||||
} // namespace Webview
|
||||
351
Telegram/lib_webview/webview/webview_embed.cpp
Normal file
351
Telegram/lib_webview/webview/webview_embed.cpp
Normal file
@@ -0,0 +1,351 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#include "webview/webview_embed.h"
|
||||
|
||||
#include "webview/webview_data_stream.h"
|
||||
#include "webview/webview_dialog.h"
|
||||
#include "webview/webview_interface.h"
|
||||
#include "base/debug_log.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "base/options.h"
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/platform/base_platform_info.h"
|
||||
#include "base/integration.h"
|
||||
|
||||
#include <QtWidgets/QWidget>
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtCore/QJsonDocument>
|
||||
|
||||
#include <charconv>
|
||||
|
||||
namespace Webview {
|
||||
namespace {
|
||||
|
||||
base::options::toggle OptionWebviewDebugEnabled({
|
||||
.id = kOptionWebviewDebugEnabled,
|
||||
.name = "Enable webview inspecting",
|
||||
.description = "Right click and choose Inspect in the webview windows. (on macOS launch Safari, open from Develop menu)",
|
||||
});
|
||||
|
||||
base::options::toggle OptionWebviewLegacyEdge({
|
||||
.id = kOptionWebviewLegacyEdge,
|
||||
.name = "Force legacy Edge WebView.",
|
||||
.description = "Skip modern CoreWebView2 check and force using legacy Edge WebView on Windows.",
|
||||
.scope = base::options::windows,
|
||||
.restartRequired = true,
|
||||
});
|
||||
|
||||
} // namespace
|
||||
|
||||
const char kOptionWebviewDebugEnabled[] = "webview-debug-enabled";
|
||||
|
||||
const char kOptionWebviewLegacyEdge[] = "webview-legacy-edge";
|
||||
|
||||
Window::Window(QWidget *parent, WindowConfig config) {
|
||||
if (createWebView(parent, config)) {
|
||||
setDialogHandler(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
Window::~Window() = default;
|
||||
|
||||
bool Window::createWebView(QWidget *parent, const WindowConfig &config) {
|
||||
Expects(!_webview);
|
||||
|
||||
_webview = CreateInstance({
|
||||
.parent = parent,
|
||||
.opaqueBg = config.opaqueBg,
|
||||
.messageHandler = messageHandler(),
|
||||
.navigationStartHandler = navigationStartHandler(),
|
||||
.navigationDoneHandler = navigationDoneHandler(),
|
||||
.dialogHandler = dialogHandler(),
|
||||
.dataRequestHandler = dataRequestHandler(),
|
||||
.dataProtocolOverride = config.dataProtocolOverride.toStdString(),
|
||||
.userDataPath = config.storageId.path.toStdString(),
|
||||
.userDataToken = config.storageId.token.toStdString(),
|
||||
.debug = OptionWebviewDebugEnabled.value(),
|
||||
.safe = config.safe,
|
||||
});
|
||||
return (_webview != nullptr);
|
||||
}
|
||||
|
||||
QWidget *Window::widget() const {
|
||||
return _webview ? _webview->widget() : nullptr;
|
||||
}
|
||||
|
||||
void Window::updateTheme(
|
||||
QColor opaqueBg,
|
||||
QColor scrollBg,
|
||||
QColor scrollBgOver,
|
||||
QColor scrollBarBg,
|
||||
QColor scrollBarBgOver) {
|
||||
if (!_webview) {
|
||||
return;
|
||||
}
|
||||
#ifndef Q_OS_MAC
|
||||
const auto wrap = [](QColor color) {
|
||||
return u"rgba(%1, %2, %3, %4)"_q
|
||||
.arg(color.red())
|
||||
.arg(color.green())
|
||||
.arg(color.blue())
|
||||
.arg(color.alphaF()).toStdString();
|
||||
};
|
||||
const auto function = R"(
|
||||
function() {
|
||||
const style = document.createElement('style');
|
||||
style.textContent = ' \
|
||||
::-webkit-scrollbar { \
|
||||
border-radius: 5px !important; \
|
||||
border: 3px solid transparent !important; \
|
||||
background-color: )" + wrap(scrollBg) + R"( !important; \
|
||||
background-clip: content-box !important; \
|
||||
width: 10px !important; \
|
||||
} \
|
||||
::-webkit-scrollbar:hover { \
|
||||
background-color: )" + wrap(scrollBgOver) + R"( !important; \
|
||||
} \
|
||||
::-webkit-scrollbar-thumb { \
|
||||
border-radius: 5px !important; \
|
||||
border: 3px solid transparent !important; \
|
||||
background-color: )" + wrap(scrollBarBg) + R"( !important; \
|
||||
background-clip: content-box !important; \
|
||||
} \
|
||||
::-webkit-scrollbar-thumb:hover { \
|
||||
background-color: )" + wrap(scrollBarBgOver) + R"( !important; \
|
||||
} \
|
||||
';
|
||||
document.head.append(style);
|
||||
}
|
||||
)";
|
||||
_webview->init(
|
||||
"document.addEventListener('DOMContentLoaded', "
|
||||
+ function
|
||||
+ ", false);");
|
||||
_webview->eval("(" + function + "());");
|
||||
#endif
|
||||
_webview->setOpaqueBg(opaqueBg);
|
||||
}
|
||||
|
||||
void Window::navigate(const QString &url) {
|
||||
Expects(_webview != nullptr);
|
||||
|
||||
_webview->navigate(url.toStdString());
|
||||
}
|
||||
|
||||
void Window::navigateToData(const QString &id) {
|
||||
Expects(_webview != nullptr);
|
||||
|
||||
_webview->navigateToData(id.toStdString());
|
||||
}
|
||||
|
||||
void Window::reload() {
|
||||
Expects(_webview != nullptr);
|
||||
|
||||
_webview->reload();
|
||||
}
|
||||
|
||||
void Window::init(const QByteArray &js) {
|
||||
Expects(_webview != nullptr);
|
||||
|
||||
_webview->init(js.toStdString());
|
||||
}
|
||||
|
||||
void Window::eval(const QByteArray &js) {
|
||||
Expects(_webview != nullptr);
|
||||
|
||||
_webview->eval(js.toStdString());
|
||||
}
|
||||
|
||||
void Window::focus() {
|
||||
Expects(_webview != nullptr);
|
||||
|
||||
_webview->focus();
|
||||
}
|
||||
|
||||
void Window::refreshNavigationHistoryState() {
|
||||
Expects(_webview != nullptr);
|
||||
|
||||
_webview->refreshNavigationHistoryState();
|
||||
}
|
||||
|
||||
auto Window::navigationHistoryState() const
|
||||
-> rpl::producer<NavigationHistoryState>{
|
||||
Expects(_webview != nullptr);
|
||||
|
||||
return [data = _webview->navigationHistoryState()](
|
||||
auto consumer) mutable {
|
||||
auto result = rpl::lifetime();
|
||||
|
||||
std::move(
|
||||
data
|
||||
) | rpl::on_next([=](NavigationHistoryState state) {
|
||||
base::Integration::Instance().enterFromEventLoop([&] {
|
||||
consumer.put_next_copy(state);
|
||||
});
|
||||
}, result);
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
ZoomController *Window::zoomController() const {
|
||||
return _webview ? _webview->zoomController() : nullptr;
|
||||
}
|
||||
|
||||
void Window::setMessageHandler(Fn<void(std::string)> handler) {
|
||||
_messageHandler = std::move(handler);
|
||||
}
|
||||
|
||||
void Window::setMessageHandler(Fn<void(const QJsonDocument&)> handler) {
|
||||
if (!handler) {
|
||||
setMessageHandler(Fn<void(std::string)>());
|
||||
return;
|
||||
}
|
||||
setMessageHandler([=](std::string text) {
|
||||
auto error = QJsonParseError();
|
||||
auto document = QJsonDocument::fromJson(
|
||||
QByteArray::fromRawData(text.data(), text.size()),
|
||||
&error);
|
||||
if (error.error == QJsonParseError::NoError) {
|
||||
handler(std::move(document));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Fn<void(std::string)> Window::messageHandler() const {
|
||||
return [=](std::string message) {
|
||||
if (_messageHandler) {
|
||||
base::Integration::Instance().enterFromEventLoop([&] {
|
||||
_messageHandler(std::move(message));
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void Window::setNavigationStartHandler(Fn<bool(QString,bool)> handler) {
|
||||
if (!handler) {
|
||||
_navigationStartHandler = nullptr;
|
||||
return;
|
||||
}
|
||||
_navigationStartHandler = [=](std::string uri, bool newWindow) {
|
||||
return handler(QString::fromStdString(uri), newWindow);
|
||||
};
|
||||
}
|
||||
|
||||
void Window::setNavigationDoneHandler(Fn<void(bool)> handler) {
|
||||
_navigationDoneHandler = std::move(handler);
|
||||
}
|
||||
|
||||
void Window::setDialogHandler(Fn<DialogResult(DialogArgs)> handler) {
|
||||
_dialogHandler = handler ? handler : DefaultDialogHandler;
|
||||
}
|
||||
|
||||
void Window::setDataRequestHandler(Fn<DataResult(DataRequest)> handler) {
|
||||
_dataRequestHandler = std::move(handler);
|
||||
}
|
||||
|
||||
Fn<bool(std::string,bool)> Window::navigationStartHandler() const {
|
||||
return [=](std::string message, bool newWindow) {
|
||||
const auto lower = QString::fromStdString(message).toLower();
|
||||
if (!lower.startsWith(u"http://"_q)
|
||||
&& !lower.startsWith(u"https://"_q)
|
||||
&& !lower.startsWith(u"tonsite://"_q)
|
||||
&& !lower.startsWith(u"ton://"_q)) {
|
||||
return false;
|
||||
}
|
||||
auto result = true;
|
||||
if (_navigationStartHandler) {
|
||||
base::Integration::Instance().enterFromEventLoop([&] {
|
||||
result = _navigationStartHandler(
|
||||
std::move(message),
|
||||
newWindow);
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
Fn<void(bool)> Window::navigationDoneHandler() const {
|
||||
return [=](bool success) {
|
||||
if (_navigationDoneHandler) {
|
||||
base::Integration::Instance().enterFromEventLoop([&] {
|
||||
_navigationDoneHandler(success);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Fn<DialogResult(DialogArgs)> Window::dialogHandler() const {
|
||||
return [=](DialogArgs args) {
|
||||
auto result = DialogResult();
|
||||
if (_dialogHandler) {
|
||||
base::Integration::Instance().enterFromEventLoop([&] {
|
||||
args.parent = widget();
|
||||
result = _dialogHandler(std::move(args));
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
Fn<DataResult(DataRequest)> Window::dataRequestHandler() const {
|
||||
return [=](DataRequest request) {
|
||||
return _dataRequestHandler
|
||||
? _dataRequestHandler(std::move(request))
|
||||
: DataResult::Failed;
|
||||
};
|
||||
}
|
||||
|
||||
void ParseRangeHeaderFor(DataRequest &request, std::string_view header) {
|
||||
const auto unsupported = [&] {
|
||||
LOG(("Unsupported range header: ")
|
||||
+ QString::fromUtf8(header.data(), header.size()));
|
||||
};
|
||||
if (header.compare(0, 6, "bytes=")) {
|
||||
return unsupported();
|
||||
}
|
||||
const auto range = std::string_view(header).substr(6);
|
||||
const auto separator = range.find('-');
|
||||
if (separator == range.npos) {
|
||||
return unsupported();
|
||||
}
|
||||
const auto startFrom = range.data();
|
||||
const auto startTill = startFrom + separator;
|
||||
const auto finishFrom = startTill + 1;
|
||||
const auto finishTill = startFrom + range.size();
|
||||
if (finishTill > finishFrom) {
|
||||
const auto done = std::from_chars(
|
||||
finishFrom,
|
||||
finishTill,
|
||||
request.limit);
|
||||
if (done.ec != std::errc() || done.ptr != finishTill) {
|
||||
request.limit = 0;
|
||||
return unsupported();
|
||||
}
|
||||
request.limit += 1; // 0-499 means first 500 bytes.
|
||||
} else {
|
||||
request.limit = -1;
|
||||
}
|
||||
if (startTill > startFrom) {
|
||||
const auto done = std::from_chars(
|
||||
startFrom,
|
||||
startTill,
|
||||
request.offset);
|
||||
if (done.ec != std::errc() || done.ptr != startTill) {
|
||||
request.offset = request.limit = 0;
|
||||
return unsupported();
|
||||
} else if (request.limit > 0) {
|
||||
request.limit -= request.offset;
|
||||
if (request.limit <= 0) {
|
||||
request.offset = request.limit = 0;
|
||||
return unsupported();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Webview
|
||||
100
Telegram/lib_webview/webview/webview_embed.h
Normal file
100
Telegram/lib_webview/webview/webview_embed.h
Normal file
@@ -0,0 +1,100 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "base/unique_qptr.h"
|
||||
#include "base/basic_types.h"
|
||||
#include "webview/webview_common.h"
|
||||
|
||||
#include <rpl/lifetime.h>
|
||||
#include <rpl/producer.h>
|
||||
#include <QColor>
|
||||
|
||||
class QString;
|
||||
class QWidget;
|
||||
class QWindow;
|
||||
|
||||
namespace Webview {
|
||||
|
||||
extern const char kOptionWebviewDebugEnabled[];
|
||||
extern const char kOptionWebviewLegacyEdge[];
|
||||
|
||||
struct DialogArgs;
|
||||
struct DialogResult;
|
||||
class Interface;
|
||||
class ZoomController;
|
||||
struct Config;
|
||||
struct DataRequest;
|
||||
enum class DataResult;
|
||||
struct NavigationHistoryState;
|
||||
|
||||
struct WindowConfig {
|
||||
QColor opaqueBg;
|
||||
StorageId storageId;
|
||||
QString dataProtocolOverride;
|
||||
bool safe = false;
|
||||
};
|
||||
|
||||
class Window final {
|
||||
public:
|
||||
explicit Window(
|
||||
QWidget *parent = nullptr,
|
||||
WindowConfig config = WindowConfig());
|
||||
~Window();
|
||||
|
||||
// May be nullptr or destroyed any time (in case webview crashed).
|
||||
[[nodiscard]] QWidget *widget() const;
|
||||
|
||||
void updateTheme(
|
||||
QColor opaqueBg,
|
||||
QColor scrollBg,
|
||||
QColor scrollBgOver,
|
||||
QColor scrollBarBg,
|
||||
QColor scrollBarBgOver);
|
||||
void navigate(const QString &url);
|
||||
void navigateToData(const QString &id);
|
||||
void reload();
|
||||
void setMessageHandler(Fn<void(std::string)> handler);
|
||||
void setMessageHandler(Fn<void(const QJsonDocument&)> handler);
|
||||
void setNavigationStartHandler(Fn<bool(QString,bool)> handler);
|
||||
void setNavigationDoneHandler(Fn<void(bool)> handler);
|
||||
void setDialogHandler(Fn<DialogResult(DialogArgs)> handler);
|
||||
void setDataRequestHandler(Fn<DataResult(DataRequest)> handler);
|
||||
void init(const QByteArray &js);
|
||||
void eval(const QByteArray &js);
|
||||
|
||||
void focus();
|
||||
|
||||
void refreshNavigationHistoryState();
|
||||
[[nodiscard]] auto navigationHistoryState() const
|
||||
-> rpl::producer<NavigationHistoryState>;
|
||||
|
||||
[[nodiscard]] ZoomController *zoomController() const;
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
|
||||
private:
|
||||
bool createWebView(QWidget *parent, const WindowConfig &config);
|
||||
[[nodiscard]] Fn<void(std::string)> messageHandler() const;
|
||||
[[nodiscard]] Fn<bool(std::string,bool)> navigationStartHandler() const;
|
||||
[[nodiscard]] Fn<void(bool)> navigationDoneHandler() const;
|
||||
[[nodiscard]] Fn<DialogResult(DialogArgs)> dialogHandler() const;
|
||||
[[nodiscard]] Fn<DataResult(DataRequest)> dataRequestHandler() const;
|
||||
|
||||
std::unique_ptr<Interface> _webview;
|
||||
Fn<void(std::string)> _messageHandler;
|
||||
Fn<bool(std::string,bool)> _navigationStartHandler;
|
||||
Fn<void(bool)> _navigationDoneHandler;
|
||||
Fn<DialogResult(DialogArgs)> _dialogHandler;
|
||||
Fn<DataResult(DataRequest)> _dataRequestHandler;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Webview
|
||||
160
Telegram/lib_webview/webview/webview_interface.h
Normal file
160
Telegram/lib_webview/webview/webview_interface.h
Normal file
@@ -0,0 +1,160 @@
|
||||
// This file is part of Desktop App Toolkit,
|
||||
// a set of libraries for developing nice desktop applications.
|
||||
//
|
||||
// For license and copyright information please follow this link:
|
||||
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "webview/webview_common.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
|
||||
#include <rpl/never.h>
|
||||
#include <rpl/producer.h>
|
||||
|
||||
#include <QtGui/QColor>
|
||||
|
||||
// Inspired by https://github.com/webview/webview.
|
||||
|
||||
class QWidget;
|
||||
|
||||
namespace Webview {
|
||||
|
||||
class DataStream;
|
||||
|
||||
struct NavigationHistoryState {
|
||||
std::string url;
|
||||
std::string title;
|
||||
bool canGoBack : 1 = false;
|
||||
bool canGoForward : 1 = false;
|
||||
|
||||
friend inline constexpr bool operator==(
|
||||
NavigationHistoryState,
|
||||
NavigationHistoryState) = default;
|
||||
};
|
||||
|
||||
class ZoomController {
|
||||
public:
|
||||
ZoomController() = default;
|
||||
|
||||
[[nodiscard]] virtual rpl::producer<int> zoomValue() {
|
||||
return rpl::never<int>();
|
||||
}
|
||||
virtual void setZoom(int) {
|
||||
}
|
||||
};
|
||||
|
||||
class Interface {
|
||||
public:
|
||||
virtual ~Interface() = default;
|
||||
|
||||
virtual void navigate(std::string url) = 0;
|
||||
virtual void navigateToData(std::string id) = 0;
|
||||
virtual void reload() = 0;
|
||||
|
||||
virtual void init(std::string js) = 0;
|
||||
virtual void eval(std::string js) = 0;
|
||||
|
||||
virtual void focus() = 0;
|
||||
|
||||
virtual void setOpaqueBg(QColor opaqueBg) = 0;
|
||||
|
||||
[[nodiscard]] virtual QWidget *widget() = 0;
|
||||
|
||||
virtual void refreshNavigationHistoryState() = 0;
|
||||
[[nodiscard]] virtual auto navigationHistoryState()
|
||||
-> rpl::producer<NavigationHistoryState> = 0;
|
||||
|
||||
[[nodiscard]] virtual ZoomController *zoomController() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
enum class DialogType {
|
||||
Alert,
|
||||
Confirm,
|
||||
Prompt,
|
||||
};
|
||||
|
||||
struct DialogArgs {
|
||||
QWidget *parent = nullptr;
|
||||
DialogType type = DialogType::Alert;
|
||||
std::string value;
|
||||
std::string text;
|
||||
std::string url;
|
||||
};
|
||||
|
||||
struct DialogResult {
|
||||
std::string text;
|
||||
bool accepted = false;
|
||||
};
|
||||
|
||||
struct DataResponse {
|
||||
std::unique_ptr<DataStream> stream;
|
||||
std::int64_t streamOffset = 0;
|
||||
std::int64_t totalSize = 0;
|
||||
};
|
||||
|
||||
struct DataRequest {
|
||||
std::string id;
|
||||
std::int64_t offset = 0;
|
||||
std::int64_t limit = 0; // < 0 means "Range: bytes=offset-" header.
|
||||
std::function<void(DataResponse)> done;
|
||||
};
|
||||
|
||||
enum class DataResult {
|
||||
Done,
|
||||
Pending,
|
||||
Failed,
|
||||
};
|
||||
|
||||
struct Config {
|
||||
QWidget *parent = nullptr;
|
||||
QColor opaqueBg;
|
||||
std::function<void(std::string)> messageHandler;
|
||||
std::function<bool(std::string,bool)> navigationStartHandler;
|
||||
std::function<void(bool)> navigationDoneHandler;
|
||||
std::function<DialogResult(DialogArgs)> dialogHandler;
|
||||
std::function<DataResult(DataRequest)> dataRequestHandler;
|
||||
std::string dataProtocolOverride;
|
||||
std::string userDataPath;
|
||||
std::string userDataToken;
|
||||
bool debug = false;
|
||||
bool safe = false;
|
||||
};
|
||||
|
||||
struct Available {
|
||||
enum class Error {
|
||||
None,
|
||||
NoWebview2,
|
||||
NoWebKitGTK,
|
||||
OldWindows,
|
||||
};
|
||||
Error error = Error::None;
|
||||
bool customSchemeRequests = false;
|
||||
bool customRangeRequests = false;
|
||||
bool customReferer = false;
|
||||
std::string details;
|
||||
};
|
||||
|
||||
void ParseRangeHeaderFor(DataRequest &request, std::string_view header);
|
||||
|
||||
[[nodiscard]] Available Availability();
|
||||
[[nodiscard]] inline bool Supported() {
|
||||
return Availability().error == Available::Error::None;
|
||||
}
|
||||
[[nodiscard]] bool SupportsEmbedAfterCreate();
|
||||
[[nodiscard]] bool SeparateStorageIdSupported();
|
||||
|
||||
// HWND on Windows, nullptr on macOS, GtkWindow on Linux.
|
||||
[[nodiscard]] std::unique_ptr<Interface> CreateInstance(Config config);
|
||||
|
||||
[[nodiscard]] std::string GenerateStorageToken();
|
||||
void ClearStorageDataByToken(const std::string &token);
|
||||
|
||||
} // namespace Webview
|
||||
Reference in New Issue
Block a user