webview_embed.cpp 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. // This file is part of Desktop App Toolkit,
  2. // a set of libraries for developing nice desktop applications.
  3. //
  4. // For license and copyright information please follow this link:
  5. // https://github.com/desktop-app/legal/blob/master/LEGAL
  6. //
  7. #include "webview/webview_embed.h"
  8. #include "webview/webview_data_stream.h"
  9. #include "webview/webview_dialog.h"
  10. #include "webview/webview_interface.h"
  11. #include "base/debug_log.h"
  12. #include "base/event_filter.h"
  13. #include "base/options.h"
  14. #include "base/invoke_queued.h"
  15. #include "base/platform/base_platform_info.h"
  16. #include "base/integration.h"
  17. #include <QtWidgets/QWidget>
  18. #include <QtGui/QWindow>
  19. #include <QtCore/QJsonDocument>
  20. #include <charconv>
  21. namespace Webview {
  22. namespace {
  23. base::options::toggle OptionWebviewDebugEnabled({
  24. .id = kOptionWebviewDebugEnabled,
  25. .name = "Enable webview inspecting",
  26. .description = "Right click and choose Inspect in the webview windows. (on macOS launch Safari, open from Develop menu)",
  27. });
  28. base::options::toggle OptionWebviewLegacyEdge({
  29. .id = kOptionWebviewLegacyEdge,
  30. .name = "Force legacy Edge WebView.",
  31. .description = "Skip modern CoreWebView2 check and force using legacy Edge WebView on Windows.",
  32. .scope = base::options::windows,
  33. .restartRequired = true,
  34. });
  35. } // namespace
  36. const char kOptionWebviewDebugEnabled[] = "webview-debug-enabled";
  37. const char kOptionWebviewLegacyEdge[] = "webview-legacy-edge";
  38. Window::Window(QWidget *parent, WindowConfig config) {
  39. if (createWebView(parent, config)) {
  40. setDialogHandler(nullptr);
  41. }
  42. }
  43. Window::~Window() = default;
  44. bool Window::createWebView(QWidget *parent, const WindowConfig &config) {
  45. Expects(!_webview);
  46. _webview = CreateInstance({
  47. .parent = parent,
  48. .opaqueBg = config.opaqueBg,
  49. .messageHandler = messageHandler(),
  50. .navigationStartHandler = navigationStartHandler(),
  51. .navigationDoneHandler = navigationDoneHandler(),
  52. .dialogHandler = dialogHandler(),
  53. .dataRequestHandler = dataRequestHandler(),
  54. .dataProtocolOverride = config.dataProtocolOverride.toStdString(),
  55. .userDataPath = config.storageId.path.toStdString(),
  56. .userDataToken = config.storageId.token.toStdString(),
  57. .debug = OptionWebviewDebugEnabled.value(),
  58. });
  59. return (_webview != nullptr);
  60. }
  61. QWidget *Window::widget() const {
  62. return _webview ? _webview->widget() : nullptr;
  63. }
  64. void Window::updateTheme(
  65. QColor opaqueBg,
  66. QColor scrollBg,
  67. QColor scrollBgOver,
  68. QColor scrollBarBg,
  69. QColor scrollBarBgOver) {
  70. if (!_webview) {
  71. return;
  72. }
  73. #ifndef Q_OS_MAC
  74. const auto wrap = [](QColor color) {
  75. return u"rgba(%1, %2, %3, %4)"_q
  76. .arg(color.red())
  77. .arg(color.green())
  78. .arg(color.blue())
  79. .arg(color.alphaF()).toStdString();
  80. };
  81. const auto function = R"(
  82. function() {
  83. const style = document.createElement('style');
  84. style.textContent = ' \
  85. ::-webkit-scrollbar { \
  86. border-radius: 5px !important; \
  87. border: 3px solid transparent !important; \
  88. background-color: )" + wrap(scrollBg) + R"( !important; \
  89. background-clip: content-box !important; \
  90. width: 10px !important; \
  91. } \
  92. ::-webkit-scrollbar:hover { \
  93. background-color: )" + wrap(scrollBgOver) + R"( !important; \
  94. } \
  95. ::-webkit-scrollbar-thumb { \
  96. border-radius: 5px !important; \
  97. border: 3px solid transparent !important; \
  98. background-color: )" + wrap(scrollBarBg) + R"( !important; \
  99. background-clip: content-box !important; \
  100. } \
  101. ::-webkit-scrollbar-thumb:hover { \
  102. background-color: )" + wrap(scrollBarBgOver) + R"( !important; \
  103. } \
  104. ';
  105. document.head.append(style);
  106. }
  107. )";
  108. _webview->init(
  109. "document.addEventListener('DOMContentLoaded', "
  110. + function
  111. + ", false);");
  112. _webview->eval("(" + function + "());");
  113. #endif
  114. _webview->setOpaqueBg(opaqueBg);
  115. }
  116. void Window::navigate(const QString &url) {
  117. Expects(_webview != nullptr);
  118. _webview->navigate(url.toStdString());
  119. }
  120. void Window::navigateToData(const QString &id) {
  121. Expects(_webview != nullptr);
  122. _webview->navigateToData(id.toStdString());
  123. }
  124. void Window::reload() {
  125. Expects(_webview != nullptr);
  126. _webview->reload();
  127. }
  128. void Window::init(const QByteArray &js) {
  129. Expects(_webview != nullptr);
  130. _webview->init(js.toStdString());
  131. }
  132. void Window::eval(const QByteArray &js) {
  133. Expects(_webview != nullptr);
  134. _webview->eval(js.toStdString());
  135. }
  136. void Window::focus() {
  137. Expects(_webview != nullptr);
  138. _webview->focus();
  139. }
  140. void Window::refreshNavigationHistoryState() {
  141. Expects(_webview != nullptr);
  142. _webview->refreshNavigationHistoryState();
  143. }
  144. auto Window::navigationHistoryState() const
  145. -> rpl::producer<NavigationHistoryState>{
  146. Expects(_webview != nullptr);
  147. return [data = _webview->navigationHistoryState()](
  148. auto consumer) mutable {
  149. auto result = rpl::lifetime();
  150. std::move(
  151. data
  152. ) | rpl::start_with_next([=](NavigationHistoryState state) {
  153. base::Integration::Instance().enterFromEventLoop([&] {
  154. consumer.put_next_copy(state);
  155. });
  156. }, result);
  157. return result;
  158. };
  159. }
  160. ZoomController *Window::zoomController() const {
  161. return _webview ? _webview->zoomController() : nullptr;
  162. }
  163. void Window::setMessageHandler(Fn<void(std::string)> handler) {
  164. _messageHandler = std::move(handler);
  165. }
  166. void Window::setMessageHandler(Fn<void(const QJsonDocument&)> handler) {
  167. if (!handler) {
  168. setMessageHandler(Fn<void(std::string)>());
  169. return;
  170. }
  171. setMessageHandler([=](std::string text) {
  172. auto error = QJsonParseError();
  173. auto document = QJsonDocument::fromJson(
  174. QByteArray::fromRawData(text.data(), text.size()),
  175. &error);
  176. if (error.error == QJsonParseError::NoError) {
  177. handler(std::move(document));
  178. }
  179. });
  180. }
  181. Fn<void(std::string)> Window::messageHandler() const {
  182. return [=](std::string message) {
  183. if (_messageHandler) {
  184. base::Integration::Instance().enterFromEventLoop([&] {
  185. _messageHandler(std::move(message));
  186. });
  187. }
  188. };
  189. }
  190. void Window::setNavigationStartHandler(Fn<bool(QString,bool)> handler) {
  191. if (!handler) {
  192. _navigationStartHandler = nullptr;
  193. return;
  194. }
  195. _navigationStartHandler = [=](std::string uri, bool newWindow) {
  196. return handler(QString::fromStdString(uri), newWindow);
  197. };
  198. }
  199. void Window::setNavigationDoneHandler(Fn<void(bool)> handler) {
  200. _navigationDoneHandler = std::move(handler);
  201. }
  202. void Window::setDialogHandler(Fn<DialogResult(DialogArgs)> handler) {
  203. _dialogHandler = handler ? handler : DefaultDialogHandler;
  204. }
  205. void Window::setDataRequestHandler(Fn<DataResult(DataRequest)> handler) {
  206. _dataRequestHandler = std::move(handler);
  207. }
  208. Fn<bool(std::string,bool)> Window::navigationStartHandler() const {
  209. return [=](std::string message, bool newWindow) {
  210. const auto lower = QString::fromStdString(message).toLower();
  211. if (!lower.startsWith("http://") && !lower.startsWith("https://")) {
  212. return false;
  213. }
  214. auto result = true;
  215. if (_navigationStartHandler) {
  216. base::Integration::Instance().enterFromEventLoop([&] {
  217. result = _navigationStartHandler(
  218. std::move(message),
  219. newWindow);
  220. });
  221. }
  222. return result;
  223. };
  224. }
  225. Fn<void(bool)> Window::navigationDoneHandler() const {
  226. return [=](bool success) {
  227. if (_navigationDoneHandler) {
  228. base::Integration::Instance().enterFromEventLoop([&] {
  229. _navigationDoneHandler(success);
  230. });
  231. }
  232. };
  233. }
  234. Fn<DialogResult(DialogArgs)> Window::dialogHandler() const {
  235. return [=](DialogArgs args) {
  236. auto result = DialogResult();
  237. if (_dialogHandler) {
  238. base::Integration::Instance().enterFromEventLoop([&] {
  239. args.parent = widget();
  240. result = _dialogHandler(std::move(args));
  241. });
  242. }
  243. return result;
  244. };
  245. }
  246. Fn<DataResult(DataRequest)> Window::dataRequestHandler() const {
  247. return [=](DataRequest request) {
  248. return _dataRequestHandler
  249. ? _dataRequestHandler(std::move(request))
  250. : DataResult::Failed;
  251. };
  252. }
  253. void ParseRangeHeaderFor(DataRequest &request, std::string_view header) {
  254. const auto unsupported = [&] {
  255. LOG(("Unsupported range header: ")
  256. + QString::fromUtf8(header.data(), header.size()));
  257. };
  258. if (header.compare(0, 6, "bytes=")) {
  259. return unsupported();
  260. }
  261. const auto range = std::string_view(header).substr(6);
  262. const auto separator = range.find('-');
  263. if (separator == range.npos) {
  264. return unsupported();
  265. }
  266. const auto startFrom = range.data();
  267. const auto startTill = startFrom + separator;
  268. const auto finishFrom = startTill + 1;
  269. const auto finishTill = startFrom + range.size();
  270. if (finishTill > finishFrom) {
  271. const auto done = std::from_chars(
  272. finishFrom,
  273. finishTill,
  274. request.limit);
  275. if (done.ec != std::errc() || done.ptr != finishTill) {
  276. request.limit = 0;
  277. return unsupported();
  278. }
  279. request.limit += 1; // 0-499 means first 500 bytes.
  280. } else {
  281. request.limit = -1;
  282. }
  283. if (startTill > startFrom) {
  284. const auto done = std::from_chars(
  285. startFrom,
  286. startTill,
  287. request.offset);
  288. if (done.ec != std::errc() || done.ptr != startTill) {
  289. request.offset = request.limit = 0;
  290. return unsupported();
  291. } else if (request.limit > 0) {
  292. request.limit -= request.offset;
  293. if (request.limit <= 0) {
  294. request.offset = request.limit = 0;
  295. return unsupported();
  296. }
  297. }
  298. }
  299. }
  300. } // namespace Webview