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

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View 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
//
#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

View 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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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"

View 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

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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