| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347 |
- // 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(),
- });
- 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::start_with_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("http://") && !lower.startsWith("https://")) {
- 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
|