// 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 #include #include #include 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{ 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 handler) { _messageHandler = std::move(handler); } void Window::setMessageHandler(Fn handler) { if (!handler) { setMessageHandler(Fn()); 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 Window::messageHandler() const { return [=](std::string message) { if (_messageHandler) { base::Integration::Instance().enterFromEventLoop([&] { _messageHandler(std::move(message)); }); } }; } void Window::setNavigationStartHandler(Fn handler) { if (!handler) { _navigationStartHandler = nullptr; return; } _navigationStartHandler = [=](std::string uri, bool newWindow) { return handler(QString::fromStdString(uri), newWindow); }; } void Window::setNavigationDoneHandler(Fn handler) { _navigationDoneHandler = std::move(handler); } void Window::setDialogHandler(Fn handler) { _dialogHandler = handler ? handler : DefaultDialogHandler; } void Window::setDataRequestHandler(Fn handler) { _dataRequestHandler = std::move(handler); } Fn 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 Window::navigationDoneHandler() const { return [=](bool success) { if (_navigationDoneHandler) { base::Integration::Instance().enterFromEventLoop([&] { _navigationDoneHandler(success); }); } }; } Fn 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 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