webview_mac.mm 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917
  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/platform/mac/webview_mac.h"
  8. #include "webview/webview_data_stream.h"
  9. #include "webview/webview_data_stream_memory.h"
  10. #include "base/algorithm.h"
  11. #include "base/debug_log.h"
  12. #include "base/unique_qptr.h"
  13. #include "base/weak_ptr.h"
  14. #include "base/flat_map.h"
  15. #include <crl/crl_on_main.h>
  16. #include <crl/crl_time.h>
  17. #include <rpl/rpl.h>
  18. #include <QtCore/QUrl>
  19. #include <QtGui/QDesktopServices>
  20. #include <QtGui/QWindow>
  21. #include <QtWidgets/QWidget>
  22. #import <Foundation/Foundation.h>
  23. #import <WebKit/WebKit.h>
  24. namespace {
  25. constexpr auto kDataUrlScheme = std::string_view("desktop-app-resource");
  26. constexpr auto kFullDomain = std::string_view("desktop-app-resource://domain/");
  27. constexpr auto kPartsCacheLimit = 32 * 1024 * 1024;
  28. constexpr auto kUuidSize = 16;
  29. using TaskPointer = id<WKURLSchemeTask>;
  30. [[nodiscard]] NSString *stdToNS(std::string_view value) {
  31. return [[NSString alloc]
  32. initWithBytes:value.data()
  33. length:value.length()
  34. encoding:NSUTF8StringEncoding];
  35. }
  36. [[nodiscard]] std::unique_ptr<char[]> WrapBytes(const char *data, int64 length) {
  37. Expects(length > 0);
  38. auto result = std::unique_ptr<char[]>(new char[length]);
  39. memcpy(result.get(), data, length);
  40. return result;
  41. }
  42. } // namespace
  43. @interface Handler : NSObject<WKScriptMessageHandler, WKNavigationDelegate, WKUIDelegate, WKURLSchemeHandler> {
  44. }
  45. - (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;
  46. - (void) userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;
  47. - (void) webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
  48. - (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context;
  49. - (void) webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation;
  50. - (void) webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error;
  51. - (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures;
  52. - (void) webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray<NSURL *> * _Nullable URLs))completionHandler;
  53. - (void) webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler;
  54. - (void) webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler;
  55. - (void) webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *result))completionHandler;
  56. - (void) webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)task;
  57. - (void) webView:(WKWebView *)webView stopURLSchemeTask:(id <WKURLSchemeTask>)task;
  58. - (void) dealloc;
  59. @end // @interface Handler
  60. @implementation Handler {
  61. std::function<void(std::string)> _messageHandler;
  62. std::function<bool(std::string,bool)> _navigationStartHandler;
  63. std::function<void(bool)> _navigationDoneHandler;
  64. std::function<Webview::DialogResult(Webview::DialogArgs)> _dialogHandler;
  65. std::function<void(id<WKURLSchemeTask> task, bool started)> _dataRequested;
  66. std::function<void()> _updateStates;
  67. std::string _dataDomain;
  68. base::flat_map<TaskPointer, NSURLSessionDataTask*> _redirectedTasks;
  69. base::has_weak_ptr _guard;
  70. }
  71. - (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 {
  72. if (self = [super init]) {
  73. _messageHandler = std::move(messageHandler);
  74. _navigationStartHandler = std::move(navigationStartHandler);
  75. _navigationDoneHandler = std::move(navigationDoneHandler);
  76. _dialogHandler = std::move(dialogHandler);
  77. _dataRequested = std::move(dataRequested);
  78. _updateStates = std::move(updateStates);
  79. _dataDomain = std::move(dataDomain);
  80. }
  81. return self;
  82. }
  83. - (void) userContentController:(WKUserContentController *)userContentController
  84. didReceiveScriptMessage:(WKScriptMessage *)message {
  85. id body = [message body];
  86. if ([body isKindOfClass:[NSString class]]) {
  87. NSString *string = (NSString*)body;
  88. _messageHandler([string UTF8String]);
  89. }
  90. }
  91. - (void) webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
  92. NSString *string = [[[navigationAction request] URL] absoluteString];
  93. WKFrameInfo *target = [navigationAction targetFrame];
  94. const auto newWindow = !target;
  95. const auto url = [string UTF8String];
  96. if (newWindow) {
  97. if (_navigationStartHandler && _navigationStartHandler(url, true)) {
  98. QDesktopServices::openUrl(QString::fromUtf8(url));
  99. }
  100. decisionHandler(WKNavigationActionPolicyCancel);
  101. } else {
  102. if ([target isMainFrame]
  103. && !std::string(url).starts_with(_dataDomain)
  104. && _navigationStartHandler
  105. && !_navigationStartHandler(url, false)) {
  106. decisionHandler(WKNavigationActionPolicyCancel);
  107. } else {
  108. decisionHandler(WKNavigationActionPolicyAllow);
  109. }
  110. }
  111. }
  112. - (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
  113. if ([keyPath isEqualToString:@"URL"] || [keyPath isEqualToString:@"title"]) {
  114. if (_updateStates) {
  115. _updateStates();
  116. }
  117. }
  118. }
  119. - (void) webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
  120. if (_navigationDoneHandler) {
  121. _navigationDoneHandler(true);
  122. }
  123. if (_updateStates) {
  124. _updateStates();
  125. }
  126. }
  127. - (void) webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
  128. if (_navigationDoneHandler) {
  129. _navigationDoneHandler(false);
  130. }
  131. if (_updateStates) {
  132. _updateStates();
  133. }
  134. }
  135. - (nullable WKWebView *) webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
  136. NSString *string = [[[navigationAction request] URL] absoluteString];
  137. const auto url = [string UTF8String];
  138. if (_navigationStartHandler && _navigationStartHandler(url, true)) {
  139. QDesktopServices::openUrl(QString::fromUtf8(url));
  140. }
  141. return nil;
  142. }
  143. - (void) webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray<NSURL *> * _Nullable URLs))completionHandler {
  144. NSOpenPanel *openPanel = [NSOpenPanel openPanel];
  145. if (@available(macOS 10.13.4, *)) {
  146. [openPanel setCanChooseDirectories:parameters.allowsDirectories];
  147. }
  148. [openPanel setCanChooseFiles:YES];
  149. [openPanel setAllowsMultipleSelection:parameters.allowsMultipleSelection];
  150. [openPanel setResolvesAliases:YES];
  151. [openPanel beginWithCompletionHandler:^(NSInteger result){
  152. if (result == NSModalResponseOK) {
  153. completionHandler([openPanel URLs]);
  154. }
  155. }];
  156. }
  157. - (void) webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
  158. auto text = [message UTF8String];
  159. auto uri = [[[frame request] URL] absoluteString];
  160. auto url = [uri UTF8String];
  161. const auto result = _dialogHandler(Webview::DialogArgs{
  162. .type = Webview::DialogType::Alert,
  163. .text = text,
  164. .url = url,
  165. });
  166. completionHandler();
  167. }
  168. - (void) webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {
  169. auto text = [message UTF8String];
  170. auto uri = [[[frame request] URL] absoluteString];
  171. auto url = [uri UTF8String];
  172. const auto result = _dialogHandler(Webview::DialogArgs{
  173. .type = Webview::DialogType::Confirm,
  174. .text = text,
  175. .url = url,
  176. });
  177. completionHandler(result.accepted ? YES : NO);
  178. }
  179. - (void) webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *result))completionHandler {
  180. auto text = [prompt UTF8String];
  181. auto value = [defaultText UTF8String];
  182. auto uri = [[[frame request] URL] absoluteString];
  183. auto url = [uri UTF8String];
  184. const auto result = _dialogHandler(Webview::DialogArgs{
  185. .type = Webview::DialogType::Prompt,
  186. .value = value,
  187. .text = text,
  188. .url = url,
  189. });
  190. if (result.accepted) {
  191. completionHandler([NSString stringWithUTF8String:result.text.c_str()]);
  192. } else {
  193. completionHandler(nil);
  194. }
  195. }
  196. - (void) webView:(WKWebView *)webView startURLSchemeTask:(id<WKURLSchemeTask>)task {
  197. if (![self processRedirect:task]) {
  198. _dataRequested(task, true);
  199. }
  200. }
  201. - (BOOL) processRedirect:(id<WKURLSchemeTask>)task {
  202. NSString *url = task.request.URL.absoluteString;
  203. NSString *prefix = stdToNS(_dataDomain);
  204. NSString *resource = [url substringFromIndex:[prefix length]];
  205. const auto id = std::string([resource UTF8String]);
  206. const auto dot = id.find_first_of('.');
  207. const auto slash = id.find_first_of('/');
  208. if (dot == std::string::npos
  209. || slash == std::string::npos
  210. || dot > slash) {
  211. return NO;
  212. }
  213. NSMutableURLRequest *redirected = [task.request mutableCopy];
  214. redirected.URL = [NSURL URLWithString:[@"https://" stringByAppendingString:resource]];
  215. [redirected
  216. setValue:@"http://desktop-app-resource/page.html"
  217. forHTTPHeaderField:@"Referer"];
  218. const auto weak = base::make_weak(&_guard);
  219. NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession]
  220. dataTaskWithRequest:redirected
  221. completionHandler:^(
  222. NSData * _Nullable data,
  223. NSURLResponse * _Nullable response,
  224. NSError * _Nullable error) {
  225. if (response) [response retain];
  226. if (error) [error retain];
  227. if (data) [data retain];
  228. crl::on_main([=] {
  229. if (weak) {
  230. const auto i = _redirectedTasks.find(task);
  231. if (i == end(_redirectedTasks)) {
  232. return;
  233. }
  234. NSURLSessionDataTask *dataTask = i->second;
  235. _redirectedTasks.erase(i);
  236. if (error) {
  237. [task didFailWithError:error];
  238. } else {
  239. [task didReceiveResponse:response];
  240. [task didReceiveData:data];
  241. [task didFinish];
  242. }
  243. [task release];
  244. [dataTask release];
  245. }
  246. if (response) [response release];
  247. if (error) [error release];
  248. if (data) [data release];
  249. });
  250. }];
  251. [task retain];
  252. [dataTask retain];
  253. _redirectedTasks.emplace(task, dataTask);
  254. [dataTask resume];
  255. return YES;
  256. }
  257. - (void) webView:(WKWebView *)webView stopURLSchemeTask:(id <WKURLSchemeTask>)task {
  258. const auto i = _redirectedTasks.find(task);
  259. if (i != end(_redirectedTasks)) {
  260. NSURLSessionDataTask *dataTask = i->second;
  261. _redirectedTasks.erase(i);
  262. [task release];
  263. [dataTask cancel];
  264. [dataTask release];
  265. } else {
  266. _dataRequested(task, false);
  267. }
  268. }
  269. - (void) dealloc {
  270. for (const auto &[task, dataTask] : base::take(_redirectedTasks)) {
  271. NSError *error = [NSError
  272. errorWithDomain:@"org.telegram.desktop"
  273. code:404
  274. userInfo:nil];
  275. [task didFailWithError:error];
  276. [task release];
  277. [dataTask cancel];
  278. [dataTask release];
  279. }
  280. [super dealloc];
  281. }
  282. @end // @implementation Handler
  283. namespace Webview {
  284. namespace {
  285. class Instance final : public Interface, public base::has_weak_ptr {
  286. public:
  287. explicit Instance(Config config);
  288. ~Instance();
  289. void navigate(std::string url) override;
  290. void navigateToData(std::string id) override;
  291. void reload() override;
  292. void init(std::string js) override;
  293. void eval(std::string js) override;
  294. void focus() override;
  295. QWidget *widget() override;
  296. void refreshNavigationHistoryState() override;
  297. auto navigationHistoryState()
  298. -> rpl::producer<NavigationHistoryState> override;
  299. void setOpaqueBg(QColor opaqueBg) override;
  300. private:
  301. struct Task {
  302. int index = 0;
  303. crl::time started = 0;
  304. };
  305. struct PartialResource {
  306. uint32 index = 0;
  307. uint32 total = 0;
  308. std::string mime;
  309. };
  310. struct PartData {
  311. std::unique_ptr<char[]> bytes;
  312. int64 length = 0;
  313. };
  314. struct CachedResult {
  315. std::string mime;
  316. NSData *data = nil;
  317. int64 requestFrom = 0;
  318. int64 requestLength = 0;
  319. int64 total = 0;
  320. explicit operator bool() const {
  321. return data != nil;
  322. }
  323. };
  324. using CacheKey = uint64;
  325. static void TaskFail(TaskPointer task);
  326. void taskFail(TaskPointer task, int indexToCheck);
  327. void taskDone(
  328. TaskPointer task,
  329. int indexToCheck,
  330. const std::string &mime,
  331. NSData *data,
  332. int64 offset,
  333. int64 total);
  334. void processDataRequest(TaskPointer task, bool started);
  335. [[nodiscard]] CachedResult fillFromCache(const DataRequest &request);
  336. void addToCache(uint32 resourceIndex, int64 offset, PartData data);
  337. void removeCacheEntry(CacheKey key);
  338. void pruneCache();
  339. void updateHistoryStates();
  340. [[nodiscard]] static CacheKey KeyFromValues(
  341. uint32 resourceIndex,
  342. int64 offset);
  343. [[nodiscard]] static uint32 ResourceIndexFromKey(CacheKey key);
  344. [[nodiscard]] static int64 OffsetFromKey(CacheKey key);
  345. WKUserContentController *_manager = nullptr;
  346. WKWebView *_webview = nullptr;
  347. Handler *_handler = nullptr;
  348. base::unique_qptr<QWindow> _window;
  349. base::unique_qptr<QWidget> _widget;
  350. std::string _dataProtocol;
  351. std::string _dataDomain;
  352. std::function<DataResult(DataRequest)> _dataRequestHandler;
  353. rpl::variable<NavigationHistoryState> _navigationHistoryState;
  354. base::flat_map<TaskPointer, Task> _tasks;
  355. base::flat_map<std::string, PartialResource> _partialResources;
  356. base::flat_map<CacheKey, PartData> _partsCache;
  357. std::vector<CacheKey> _partsLRU;
  358. int64 _cacheTotal = 0;
  359. int _taskAutoincrement = 0;
  360. };
  361. [[nodiscard]] NSUUID *UuidFromToken(const std::string &token) {
  362. const auto bytes = reinterpret_cast<const unsigned char*>(token.data());
  363. return (token.size() == kUuidSize)
  364. ? [[NSUUID alloc] initWithUUIDBytes:bytes]
  365. : nil;
  366. }
  367. [[nodiscard]] std::string UuidToToken(NSUUID *uuid) {
  368. if (!uuid) {
  369. return std::string();
  370. }
  371. auto result = std::string(kUuidSize, ' ');
  372. const auto bytes = reinterpret_cast<unsigned char*>(result.data());
  373. [uuid getUUIDBytes:bytes];
  374. return result;
  375. }
  376. Instance::Instance(Config config) {
  377. const auto weak = base::make_weak(this);
  378. const auto handleDataRequest = [=](id<WKURLSchemeTask> task, bool started) {
  379. if (weak) {
  380. processDataRequest(task, started);
  381. } else if (started) {
  382. TaskFail(task);
  383. }
  384. };
  385. WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
  386. _manager = configuration.userContentController;
  387. _dataProtocol = kDataUrlScheme;
  388. _dataDomain = kFullDomain;
  389. if (!config.dataProtocolOverride.empty()) {
  390. _dataProtocol = config.dataProtocolOverride;
  391. _dataDomain = _dataProtocol + "://domain/";
  392. }
  393. if (config.debug) {
  394. [configuration.preferences setValue:@YES forKey:@"developerExtrasEnabled"];
  395. }
  396. const auto updateStates = [=] {
  397. updateHistoryStates();
  398. };
  399. _handler = [[Handler alloc] initWithMessageHandler:config.messageHandler navigationStartHandler:config.navigationStartHandler navigationDoneHandler:config.navigationDoneHandler dialogHandler:config.dialogHandler dataRequested:handleDataRequest updateStates:updateStates dataDomain:_dataDomain];
  400. _dataRequestHandler = std::move(config.dataRequestHandler);
  401. [configuration setURLSchemeHandler:_handler forURLScheme:stdToNS(_dataProtocol)];
  402. if (@available(macOS 14, *)) {
  403. if (config.userDataToken != LegacyStorageIdToken().toStdString()) {
  404. NSUUID *uuid = UuidFromToken(config.userDataToken);
  405. [configuration setWebsiteDataStore:[WKWebsiteDataStore dataStoreForIdentifier:uuid]];
  406. [uuid release];
  407. }
  408. }
  409. _webview = [[WKWebView alloc] initWithFrame:NSZeroRect configuration:configuration];
  410. if (@available(macOS 13.3, *)) {
  411. _webview.inspectable = config.debug ? YES : NO;
  412. }
  413. [_manager addScriptMessageHandler:_handler name:@"external"];
  414. [_webview setNavigationDelegate:_handler];
  415. [_webview setUIDelegate:_handler];
  416. [_webview addObserver:_handler forKeyPath:@"URL" options:NSKeyValueObservingOptionNew context:nil];
  417. [_webview addObserver:_handler forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil];
  418. [configuration release];
  419. _window.reset(QWindow::fromWinId(WId(_webview)));
  420. _widget.reset();
  421. _widget.reset(
  422. QWidget::createWindowContainer(
  423. _window.get(),
  424. config.parent,
  425. Qt::FramelessWindowHint));
  426. _widget->show();
  427. setOpaqueBg(config.opaqueBg);
  428. init(R"(
  429. window.external = {
  430. invoke: function(s) {
  431. window.webkit.messageHandlers.external.postMessage(s);
  432. }
  433. };)");
  434. }
  435. Instance::~Instance() {
  436. base::take(_window);
  437. base::take(_widget);
  438. [_manager removeScriptMessageHandlerForName:@"external"];
  439. [_webview setNavigationDelegate:nil];
  440. [_handler release];
  441. [_webview release];
  442. }
  443. void Instance::TaskFail(TaskPointer task) {
  444. [task didFailWithError:[NSError errorWithDomain:@"org.telegram.desktop" code:404 userInfo:nil]];
  445. }
  446. void Instance::taskFail(TaskPointer task, int indexToCheck) {
  447. if (indexToCheck) {
  448. const auto i = _tasks.find(task);
  449. if (i == end(_tasks) || i->second.index != indexToCheck) {
  450. return;
  451. }
  452. _tasks.erase(i);
  453. }
  454. TaskFail(task);
  455. }
  456. void Instance::taskDone(
  457. TaskPointer task,
  458. int indexToCheck,
  459. const std::string &mime,
  460. NSData *data,
  461. int64 offset,
  462. int64 total) {
  463. Expects(data != nil);
  464. if (indexToCheck) {
  465. const auto i = _tasks.find(task);
  466. if (i == end(_tasks) || i->second.index != indexToCheck) {
  467. return;
  468. }
  469. _tasks.erase(i);
  470. }
  471. const auto length = int64([data length]);
  472. const auto partial = (offset > 0) || (total != length);
  473. NSMutableDictionary *headers = [@{
  474. @"Content-Type": stdToNS(mime),
  475. @"Accept-Ranges": @"bytes",
  476. @"Cache-Control": @"no-store",
  477. @"Content-Length": stdToNS(std::to_string(length)),
  478. } mutableCopy];
  479. if (partial) {
  480. headers[@"Content-Range"] = stdToNS("bytes "
  481. + std::to_string(offset)
  482. + '-'
  483. + std::to_string(offset + length - 1)
  484. + '/'
  485. + std::to_string(total));
  486. }
  487. NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc]
  488. initWithURL:task.request.URL
  489. statusCode:(partial ? 206 : 200)
  490. HTTPVersion:@"HTTP/1.1"
  491. headerFields:headers];
  492. [task didReceiveResponse:response];
  493. [task didReceiveData:data];
  494. [task didFinish];
  495. }
  496. Instance::CacheKey Instance::KeyFromValues(uint32 resourceIndex, int64 offset) {
  497. return (uint64(resourceIndex) << 32) | uint32(offset);
  498. }
  499. uint32 Instance::ResourceIndexFromKey(CacheKey key) {
  500. return uint32(key >> 32);
  501. }
  502. int64 Instance::OffsetFromKey(CacheKey key) {
  503. return int64(key & 0xFFFFFFFFULL);
  504. }
  505. void Instance::addToCache(uint32 resourceIndex, int64 offset, PartData data) {
  506. auto key = KeyFromValues(resourceIndex, offset);
  507. while (true) { // Remove parts that are already in cache.
  508. auto i = _partsCache.upper_bound(key);
  509. if (i != begin(_partsCache)) {
  510. --i;
  511. const auto alreadyIndex = ResourceIndexFromKey(i->first);
  512. if (alreadyIndex == resourceIndex) {
  513. const auto &already = i->second;
  514. const auto alreadyOffset = OffsetFromKey(i->first);
  515. const auto alreadyTill = alreadyOffset + already.length;
  516. if (alreadyTill >= offset + data.length) {
  517. return; // Fully in cache.
  518. } else if (alreadyTill > offset) {
  519. const auto delta = alreadyTill - offset;
  520. offset += delta;
  521. data.length -= delta;
  522. data.bytes = WrapBytes(data.bytes.get() + delta, data.length);
  523. key = KeyFromValues(resourceIndex, offset);
  524. continue;
  525. }
  526. }
  527. ++i;
  528. }
  529. if (i != end(_partsCache)) {
  530. const auto alreadyIndex = ResourceIndexFromKey(i->first);
  531. if (alreadyIndex == resourceIndex) {
  532. const auto &already = i->second;
  533. const auto alreadyOffset = OffsetFromKey(i->first);
  534. Assert(alreadyOffset > offset);
  535. const auto alreadyTill = alreadyOffset + already.length;
  536. if (alreadyTill <= offset + data.length) {
  537. removeCacheEntry(i->first);
  538. continue;
  539. } else if (alreadyOffset < offset + data.length) {
  540. const auto delta = offset + data.length - alreadyOffset;
  541. data.length -= delta;
  542. data.bytes = WrapBytes(data.bytes.get(), data.length);
  543. continue;
  544. }
  545. }
  546. }
  547. break;
  548. }
  549. _partsLRU.push_back(key);
  550. _cacheTotal += data.length;
  551. _partsCache[key] = std::move(data);
  552. pruneCache();
  553. }
  554. void Instance::pruneCache() {
  555. while (_cacheTotal > kPartsCacheLimit) {
  556. Assert(!_partsLRU.empty());
  557. removeCacheEntry(_partsLRU.front());
  558. }
  559. }
  560. void Instance::updateHistoryStates() {
  561. NSURL *maybeUrl = [_webview URL];
  562. NSString *maybeTitle = [_webview title];
  563. const auto url = maybeUrl
  564. ? std::string([[maybeUrl absoluteString] UTF8String])
  565. : std::string();
  566. const auto title = maybeTitle
  567. ? std::string([maybeTitle UTF8String])
  568. : std::string();
  569. _navigationHistoryState = NavigationHistoryState{
  570. .url = url,
  571. .title = title,
  572. .canGoBack = ([_webview canGoBack] == YES),
  573. .canGoForward = ([_webview canGoForward] == YES),
  574. };
  575. }
  576. void Instance::removeCacheEntry(CacheKey key) {
  577. auto &part = _partsCache[key];
  578. Assert(part.length > 0);
  579. Assert(_cacheTotal >= part.length);
  580. _cacheTotal -= part.length;
  581. _partsCache.remove(key);
  582. _partsLRU.erase(
  583. std::remove(begin(_partsLRU), end(_partsLRU), key),
  584. end(_partsLRU));
  585. }
  586. Instance::CachedResult Instance::fillFromCache(
  587. const DataRequest &request) {
  588. auto &partial = _partialResources[request.id];
  589. const auto index = partial.index;
  590. if (!index) {
  591. partial.index = uint32(_partialResources.size());
  592. return {};
  593. }
  594. auto i = _partsCache.upper_bound(
  595. KeyFromValues(partial.index, request.offset));
  596. if (i == begin(_partsCache)) {
  597. return {};
  598. }
  599. --i;
  600. if (ResourceIndexFromKey(i->first) != index) {
  601. return {};
  602. }
  603. const auto alreadyOffset = OffsetFromKey(i->first);
  604. const auto alreadyTill = alreadyOffset + i->second.length;
  605. if (alreadyTill <= request.offset) {
  606. return {};
  607. }
  608. auto till = alreadyTill;
  609. for (auto j = i + 1; j != end(_partsCache); ++j) {
  610. const auto offset = OffsetFromKey(j->first);
  611. if (ResourceIndexFromKey(j->first) != index || offset > till) {
  612. break;
  613. }
  614. till = offset + j->second.length;
  615. if (request.limit <= 0 || till >= request.offset + request.limit) {
  616. break;
  617. }
  618. }
  619. const auto length = (request.limit > 0) ? request.limit : (till - request.offset);
  620. if (till < request.offset + length) {
  621. return {};
  622. }
  623. auto result = [NSMutableData dataWithLength:length];
  624. auto from = request.offset;
  625. auto fill = length;
  626. auto bytes = static_cast<char*>([result mutableBytes]);
  627. for (auto j = i; j != end(_partsCache); ++j) {
  628. const auto offset = OffsetFromKey(j->first);
  629. const auto copy = std::min(fill, offset + j->second.length - from);
  630. Assert(copy > 0);
  631. Assert(from >= offset);
  632. memcpy(bytes, j->second.bytes.get() + (from - offset), copy);
  633. from += copy;
  634. fill -= copy;
  635. bytes += copy;
  636. const auto lru = std::find(begin(_partsLRU), end(_partsLRU), j->first);
  637. Assert(lru != end(_partsLRU));
  638. if (const auto next = lru + 1; next != end(_partsLRU)) {
  639. std::rotate(lru, next, end(_partsLRU));
  640. }
  641. if (!fill) {
  642. break;
  643. }
  644. Assert(fill > 0);
  645. }
  646. Assert(fill == 0);
  647. return { .mime = partial.mime, .data = result, .total = partial.total };
  648. }
  649. void Instance::processDataRequest(TaskPointer task, bool started) {
  650. if (!started) {
  651. _tasks.remove(task);
  652. return;
  653. }
  654. @autoreleasepool {
  655. NSString *url = task.request.URL.absoluteString;
  656. NSString *prefix = stdToNS(_dataDomain);
  657. if (![url hasPrefix:prefix]) {
  658. taskFail(task, 0);
  659. return;
  660. }
  661. const auto resourceId = std::string([[url substringFromIndex:[prefix length]] UTF8String]);
  662. auto prepared = DataRequest{
  663. .id = resourceId,
  664. };
  665. NSString *rangeHeader = [task.request valueForHTTPHeaderField:@"Range"];
  666. if (rangeHeader) {
  667. ParseRangeHeaderFor(prepared, std::string([rangeHeader UTF8String]));
  668. if (const auto cached = fillFromCache(prepared)) {
  669. taskDone(task, 0, cached.mime, cached.data, prepared.offset, cached.total);
  670. return;
  671. }
  672. }
  673. const auto index = ++_taskAutoincrement;
  674. _tasks[task] = Task{ .index = index, .started = crl::now() };
  675. const auto requestedOffset = prepared.offset;
  676. const auto requestedLimit = prepared.limit;
  677. prepared.done = crl::guard(this, [=](DataResponse resolved) {
  678. auto &stream = resolved.stream;
  679. if (!stream) {
  680. return taskFail(task, index);
  681. }
  682. const auto length = stream->size();
  683. Assert(length > 0);
  684. const auto offset = resolved.streamOffset;
  685. if (requestedOffset >= offset + length || offset > requestedOffset) {
  686. return taskFail(task, index);
  687. }
  688. auto bytes = std::unique_ptr<char[]>(new char[length]);
  689. const auto read = stream->read(bytes.get(), length);
  690. Assert(read == length);
  691. const auto useLength = (requestedLimit > 0)
  692. ? std::min(requestedLimit, (offset + length - requestedOffset))
  693. : (offset + length - requestedOffset);
  694. const auto useBytes = bytes.get() + (requestedOffset - offset);
  695. const auto data = [NSData dataWithBytes:useBytes length:useLength];
  696. const auto mime = stream->mime();
  697. const auto total = resolved.totalSize ? resolved.totalSize : length;
  698. const auto i = _partialResources.find(resourceId);
  699. if (i != end(_partialResources)) {
  700. auto &partial = i->second;
  701. if (partial.mime.empty()) {
  702. partial.mime = mime;
  703. }
  704. if (!partial.total) {
  705. partial.total = total;
  706. }
  707. addToCache(partial.index, offset, { std::move(bytes), length });
  708. }
  709. taskDone(task, index, mime, data, requestedOffset, total);
  710. });
  711. const auto result = _dataRequestHandler
  712. ? _dataRequestHandler(prepared)
  713. : DataResult::Failed;
  714. if (result == DataResult::Failed) {
  715. return taskFail(task, index);
  716. }
  717. }
  718. }
  719. void Instance::navigate(std::string url) {
  720. NSString *string = [NSString stringWithUTF8String:url.c_str()];
  721. NSURL *native = [NSURL URLWithString:string];
  722. [_webview loadRequest:[NSURLRequest requestWithURL:native]];
  723. }
  724. void Instance::navigateToData(std::string id) {
  725. auto full = std::string();
  726. full.reserve(_dataDomain.size() + id.size());
  727. full.append(_dataDomain);
  728. full.append(id);
  729. navigate(full);
  730. }
  731. void Instance::reload() {
  732. [_webview reload];
  733. }
  734. void Instance::init(std::string js) {
  735. NSString *string = [NSString stringWithUTF8String:js.c_str()];
  736. WKUserScript *script = [[WKUserScript alloc] initWithSource:string injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
  737. [_manager addUserScript:script];
  738. }
  739. void Instance::eval(std::string js) {
  740. NSString *string = [NSString stringWithUTF8String:js.c_str()];
  741. [_webview evaluateJavaScript:string completionHandler:nil];
  742. }
  743. void Instance::focus() {
  744. }
  745. QWidget *Instance::widget() {
  746. return _widget.get();
  747. }
  748. void Instance::refreshNavigationHistoryState() {
  749. // Not needed here, there are events.
  750. }
  751. auto Instance::navigationHistoryState()
  752. -> rpl::producer<NavigationHistoryState> {
  753. return _navigationHistoryState.value();
  754. }
  755. void Instance::setOpaqueBg(QColor opaqueBg) {
  756. if (@available(macOS 12.0, *)) {
  757. [_webview setValue: @NO forKey: @"drawsBackground"];
  758. [_webview setUnderPageBackgroundColor:[NSColor clearColor]];
  759. }
  760. }
  761. } // namespace
  762. Available Availability() {
  763. return Available{
  764. .customSchemeRequests = true,
  765. .customRangeRequests = true,
  766. .customReferer = true,
  767. };
  768. }
  769. bool SupportsEmbedAfterCreate() {
  770. return true;
  771. }
  772. bool SeparateStorageIdSupported() {
  773. return true;
  774. }
  775. std::unique_ptr<Interface> CreateInstance(Config config) {
  776. if (!Supported()) {
  777. return nullptr;
  778. }
  779. return std::make_unique<Instance>(std::move(config));
  780. }
  781. std::string GenerateStorageToken() {
  782. return UuidToToken([NSUUID UUID]);
  783. }
  784. void ClearStorageDataByToken(const std::string &token) {
  785. if (@available(macOS 14, *)) {
  786. if (!token.empty() && token != LegacyStorageIdToken().toStdString()) {
  787. if (NSUUID *uuid = UuidFromToken(token)) {
  788. // removeDataStoreForIdentifier crashes without that (if not created first).
  789. WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
  790. [configuration setWebsiteDataStore:[WKWebsiteDataStore dataStoreForIdentifier:uuid]];
  791. [configuration release];
  792. [WKWebsiteDataStore
  793. removeDataStoreForIdentifier:uuid
  794. completionHandler:^(NSError *error) {}];
  795. [uuid release];
  796. }
  797. }
  798. }
  799. }
  800. } // namespace Webview