headless-browser.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738
  1. /*
  2. * Copyright (c) 2022, Dex♪ <dexes.ttp@gmail.com>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/Assertions.h>
  7. #include <AK/ByteBuffer.h>
  8. #include <AK/Format.h>
  9. #include <AK/HashTable.h>
  10. #include <AK/LexicalPath.h>
  11. #include <AK/NonnullOwnPtr.h>
  12. #include <AK/StringBuilder.h>
  13. #include <AK/Types.h>
  14. #include <LibCore/ArgsParser.h>
  15. #include <LibCore/EventLoop.h>
  16. #include <LibCore/File.h>
  17. #include <LibCore/IODevice.h>
  18. #include <LibCore/MemoryStream.h>
  19. #include <LibCore/Stream.h>
  20. #include <LibCore/System.h>
  21. #include <LibCore/Timer.h>
  22. #include <LibGemini/GeminiRequest.h>
  23. #include <LibGemini/GeminiResponse.h>
  24. #include <LibGemini/Job.h>
  25. #include <LibGfx/Bitmap.h>
  26. #include <LibGfx/Font/FontDatabase.h>
  27. #include <LibGfx/ImageDecoder.h>
  28. #include <LibGfx/PNGWriter.h>
  29. #include <LibGfx/Rect.h>
  30. #include <LibHTTP/HttpRequest.h>
  31. #include <LibHTTP/HttpResponse.h>
  32. #include <LibHTTP/HttpsJob.h>
  33. #include <LibHTTP/Job.h>
  34. #include <LibMain/Main.h>
  35. #include <LibWeb/Cookie/ParsedCookie.h>
  36. #include <LibWeb/DOM/Document.h>
  37. #include <LibWeb/HTML/BrowsingContext.h>
  38. #include <LibWeb/ImageDecoding.h>
  39. #include <LibWeb/Layout/InitialContainingBlock.h>
  40. #include <LibWeb/Loader/ResourceLoader.h>
  41. #include <LibWeb/Page/Page.h>
  42. #include <LibWeb/Painting/PaintableBox.h>
  43. #include <LibWeb/WebSockets/WebSocket.h>
  44. #include <LibWebSocket/ConnectionInfo.h>
  45. #include <LibWebSocket/Message.h>
  46. #include <LibWebSocket/WebSocket.h>
  47. class HeadlessBrowserPageClient final : public Web::PageClient {
  48. public:
  49. static NonnullOwnPtr<HeadlessBrowserPageClient> create()
  50. {
  51. return adopt_own(*new HeadlessBrowserPageClient());
  52. }
  53. Web::Page& page() { return *m_page; }
  54. Web::Page const& page() const { return *m_page; }
  55. Web::Layout::InitialContainingBlock* layout_root()
  56. {
  57. auto* document = page().top_level_browsing_context().active_document();
  58. if (!document)
  59. return nullptr;
  60. return document->layout_node();
  61. }
  62. void load(AK::URL const& url)
  63. {
  64. page().load(url);
  65. }
  66. void paint(Gfx::IntRect const& content_rect, Gfx::Bitmap& target)
  67. {
  68. Gfx::Painter painter(target);
  69. if (auto* document = page().top_level_browsing_context().active_document())
  70. document->update_layout();
  71. painter.fill_rect({ {}, content_rect.size() }, palette().base());
  72. auto* layout_root = this->layout_root();
  73. if (!layout_root) {
  74. return;
  75. }
  76. Web::PaintContext context(painter, palette(), content_rect.top_left());
  77. context.set_should_show_line_box_borders(false);
  78. context.set_viewport_rect(content_rect);
  79. context.set_has_focus(true);
  80. layout_root->paint_all_phases(context);
  81. }
  82. void setup_palette(Core::AnonymousBuffer theme_buffer)
  83. {
  84. m_palette_impl = Gfx::PaletteImpl::create_with_anonymous_buffer(theme_buffer);
  85. }
  86. void set_viewport_rect(Gfx::IntRect viewport_rect)
  87. {
  88. page().top_level_browsing_context().set_viewport_rect(viewport_rect);
  89. }
  90. void set_screen_rect(Gfx::IntRect screen_rect)
  91. {
  92. m_screen_rect = screen_rect;
  93. }
  94. // ^Web::PageClient
  95. virtual Gfx::Palette palette() const override
  96. {
  97. return Gfx::Palette(*m_palette_impl);
  98. }
  99. virtual Gfx::IntRect screen_rect() const override
  100. {
  101. return m_screen_rect;
  102. }
  103. virtual Web::CSS::PreferredColorScheme preferred_color_scheme() const override
  104. {
  105. return m_preferred_color_scheme;
  106. }
  107. virtual void page_did_change_title(String const&) override
  108. {
  109. }
  110. virtual void page_did_set_document_in_top_level_browsing_context(Web::DOM::Document*) override
  111. {
  112. }
  113. virtual void page_did_start_loading(AK::URL const&) override
  114. {
  115. }
  116. virtual void page_did_finish_loading(AK::URL const&) override
  117. {
  118. }
  119. virtual void page_did_change_selection() override
  120. {
  121. }
  122. virtual void page_did_request_cursor_change(Gfx::StandardCursor) override
  123. {
  124. }
  125. virtual void page_did_request_context_menu(Gfx::IntPoint const&) override
  126. {
  127. }
  128. virtual void page_did_request_link_context_menu(Gfx::IntPoint const&, AK::URL const&, String const&, unsigned) override
  129. {
  130. }
  131. virtual void page_did_request_image_context_menu(Gfx::IntPoint const&, AK::URL const&, String const&, unsigned, Gfx::Bitmap const*) override
  132. {
  133. }
  134. virtual void page_did_click_link(AK::URL const&, String const&, unsigned) override
  135. {
  136. }
  137. virtual void page_did_middle_click_link(AK::URL const&, String const&, unsigned) override
  138. {
  139. }
  140. virtual void page_did_enter_tooltip_area(Gfx::IntPoint const&, String const&) override
  141. {
  142. }
  143. virtual void page_did_leave_tooltip_area() override
  144. {
  145. }
  146. virtual void page_did_hover_link(AK::URL const&) override
  147. {
  148. }
  149. virtual void page_did_unhover_link() override
  150. {
  151. }
  152. virtual void page_did_invalidate(Gfx::IntRect const&) override
  153. {
  154. }
  155. virtual void page_did_change_favicon(Gfx::Bitmap const&) override
  156. {
  157. }
  158. virtual void page_did_layout() override
  159. {
  160. }
  161. virtual void page_did_request_scroll_into_view(Gfx::IntRect const&) override
  162. {
  163. }
  164. virtual void page_did_request_alert(String const&) override
  165. {
  166. }
  167. virtual bool page_did_request_confirm(String const&) override
  168. {
  169. return false;
  170. }
  171. virtual String page_did_request_prompt(String const&, String const&) override
  172. {
  173. return String::empty();
  174. }
  175. virtual String page_did_request_cookie(AK::URL const&, Web::Cookie::Source) override
  176. {
  177. return String::empty();
  178. }
  179. virtual void page_did_set_cookie(AK::URL const&, Web::Cookie::ParsedCookie const&, Web::Cookie::Source) override
  180. {
  181. }
  182. void request_file(NonnullRefPtr<Web::FileRequest>& request) override
  183. {
  184. auto const file = Core::System::open(request->path(), O_RDONLY);
  185. request->on_file_request_finish(file);
  186. }
  187. private:
  188. HeadlessBrowserPageClient()
  189. : m_page(make<Web::Page>(*this))
  190. {
  191. }
  192. NonnullOwnPtr<Web::Page> m_page;
  193. RefPtr<Gfx::PaletteImpl> m_palette_impl;
  194. Gfx::IntRect m_screen_rect { 0, 0, 800, 600 };
  195. Web::CSS::PreferredColorScheme m_preferred_color_scheme { Web::CSS::PreferredColorScheme::Auto };
  196. };
  197. class HeadlessImageDecoderClient : public Web::ImageDecoding::Decoder {
  198. public:
  199. static NonnullRefPtr<HeadlessImageDecoderClient> create()
  200. {
  201. return adopt_ref(*new HeadlessImageDecoderClient());
  202. }
  203. virtual ~HeadlessImageDecoderClient() override = default;
  204. virtual Optional<Web::ImageDecoding::DecodedImage> decode_image(ReadonlyBytes data) override
  205. {
  206. auto decoder = Gfx::ImageDecoder::try_create(data);
  207. if (!decoder)
  208. return Web::ImageDecoding::DecodedImage { false, 0, Vector<Web::ImageDecoding::Frame> {} };
  209. if (!decoder->frame_count())
  210. return Web::ImageDecoding::DecodedImage { false, 0, Vector<Web::ImageDecoding::Frame> {} };
  211. Vector<Web::ImageDecoding::Frame> frames;
  212. for (size_t i = 0; i < decoder->frame_count(); ++i) {
  213. auto frame_or_error = decoder->frame(i);
  214. if (frame_or_error.is_error()) {
  215. frames.append({ {}, 0 });
  216. } else {
  217. auto frame = frame_or_error.release_value();
  218. frames.append({ move(frame.image), static_cast<size_t>(frame.duration) });
  219. }
  220. }
  221. return Web::ImageDecoding::DecodedImage {
  222. decoder->is_animated(),
  223. static_cast<u32>(decoder->loop_count()),
  224. frames,
  225. };
  226. }
  227. private:
  228. explicit HeadlessImageDecoderClient() = default;
  229. };
  230. static HashTable<RefPtr<Web::ResourceLoaderConnectorRequest>> s_all_requests;
  231. class HeadlessRequestServer : public Web::ResourceLoaderConnector {
  232. public:
  233. class HTTPHeadlessRequest
  234. : public Web::ResourceLoaderConnectorRequest
  235. , public Weakable<HTTPHeadlessRequest> {
  236. public:
  237. static ErrorOr<NonnullRefPtr<HTTPHeadlessRequest>> create(String const& method, AK::URL const& url, HashMap<String, String> const& request_headers, ReadonlyBytes request_body, Core::ProxyData const&)
  238. {
  239. auto stream_backing_buffer = TRY(ByteBuffer::create_uninitialized(1 * MiB));
  240. auto underlying_socket = TRY(Core::Stream::TCPSocket::connect(url.host(), url.port().value_or(80)));
  241. TRY(underlying_socket->set_blocking(false));
  242. auto socket = TRY(Core::Stream::BufferedSocket<Core::Stream::TCPSocket>::create(move(underlying_socket)));
  243. HTTP::HttpRequest request;
  244. if (method.equals_ignoring_case("head"sv))
  245. request.set_method(HTTP::HttpRequest::HEAD);
  246. else if (method.equals_ignoring_case("get"sv))
  247. request.set_method(HTTP::HttpRequest::GET);
  248. else if (method.equals_ignoring_case("post"sv))
  249. request.set_method(HTTP::HttpRequest::POST);
  250. else
  251. request.set_method(HTTP::HttpRequest::Invalid);
  252. request.set_url(move(url));
  253. request.set_headers(request_headers);
  254. request.set_body(TRY(ByteBuffer::copy(request_body)));
  255. return adopt_ref(*new HTTPHeadlessRequest(move(request), move(socket), move(stream_backing_buffer)));
  256. }
  257. virtual ~HTTPHeadlessRequest() override
  258. {
  259. }
  260. virtual void set_should_buffer_all_input(bool) override
  261. {
  262. }
  263. virtual bool stop() override
  264. {
  265. return false;
  266. }
  267. virtual void stream_into(Core::Stream::Stream&) override
  268. {
  269. }
  270. private:
  271. HTTPHeadlessRequest(HTTP::HttpRequest&& request, NonnullOwnPtr<Core::Stream::BufferedSocketBase> socket, ByteBuffer&& stream_backing_buffer)
  272. : m_stream_backing_buffer(move(stream_backing_buffer))
  273. , m_output_stream(Core::Stream::MemoryStream::construct(m_stream_backing_buffer.bytes()).release_value_but_fixme_should_propagate_errors())
  274. , m_socket(move(socket))
  275. , m_job(HTTP::Job::construct(move(request), *m_output_stream))
  276. {
  277. m_job->on_headers_received = [weak_this = make_weak_ptr()](auto& response_headers, auto response_code) mutable {
  278. if (auto strong_this = weak_this.strong_ref()) {
  279. strong_this->m_response_code = response_code;
  280. for (auto& header : response_headers) {
  281. strong_this->m_response_headers.set(header.key, header.value);
  282. }
  283. }
  284. };
  285. m_job->on_finish = [weak_this = make_weak_ptr()](bool success) mutable {
  286. Core::deferred_invoke([weak_this, success]() mutable {
  287. if (auto strong_this = weak_this.strong_ref()) {
  288. ReadonlyBytes response_bytes { strong_this->m_output_stream->bytes().data(), strong_this->m_output_stream->offset() };
  289. auto response_buffer = ByteBuffer::copy(response_bytes).release_value_but_fixme_should_propagate_errors();
  290. strong_this->on_buffered_request_finish(success, strong_this->m_output_stream->offset(), strong_this->m_response_headers, strong_this->m_response_code, response_buffer);
  291. }
  292. });
  293. };
  294. m_job->start(*m_socket);
  295. }
  296. Optional<u32> m_response_code;
  297. ByteBuffer m_stream_backing_buffer;
  298. NonnullOwnPtr<Core::Stream::MemoryStream> m_output_stream;
  299. NonnullOwnPtr<Core::Stream::BufferedSocketBase> m_socket;
  300. NonnullRefPtr<HTTP::Job> m_job;
  301. HashMap<String, String, CaseInsensitiveStringTraits> m_response_headers;
  302. };
  303. class HTTPSHeadlessRequest
  304. : public Web::ResourceLoaderConnectorRequest
  305. , public Weakable<HTTPSHeadlessRequest> {
  306. public:
  307. static ErrorOr<NonnullRefPtr<HTTPSHeadlessRequest>> create(String const& method, AK::URL const& url, HashMap<String, String> const& request_headers, ReadonlyBytes request_body, Core::ProxyData const&)
  308. {
  309. auto stream_backing_buffer = TRY(ByteBuffer::create_uninitialized(1 * MiB));
  310. auto underlying_socket = TRY(TLS::TLSv12::connect(url.host(), url.port().value_or(443)));
  311. TRY(underlying_socket->set_blocking(false));
  312. auto socket = TRY(Core::Stream::BufferedSocket<TLS::TLSv12>::create(move(underlying_socket)));
  313. HTTP::HttpRequest request;
  314. if (method.equals_ignoring_case("head"sv))
  315. request.set_method(HTTP::HttpRequest::HEAD);
  316. else if (method.equals_ignoring_case("get"sv))
  317. request.set_method(HTTP::HttpRequest::GET);
  318. else if (method.equals_ignoring_case("post"sv))
  319. request.set_method(HTTP::HttpRequest::POST);
  320. else
  321. request.set_method(HTTP::HttpRequest::Invalid);
  322. request.set_url(move(url));
  323. request.set_headers(request_headers);
  324. request.set_body(TRY(ByteBuffer::copy(request_body)));
  325. return adopt_ref(*new HTTPSHeadlessRequest(move(request), move(socket), move(stream_backing_buffer)));
  326. }
  327. virtual ~HTTPSHeadlessRequest() override
  328. {
  329. }
  330. virtual void set_should_buffer_all_input(bool) override
  331. {
  332. }
  333. virtual bool stop() override
  334. {
  335. return false;
  336. }
  337. virtual void stream_into(Core::Stream::Stream&) override
  338. {
  339. }
  340. private:
  341. HTTPSHeadlessRequest(HTTP::HttpRequest&& request, NonnullOwnPtr<Core::Stream::BufferedSocketBase> socket, ByteBuffer&& stream_backing_buffer)
  342. : m_stream_backing_buffer(move(stream_backing_buffer))
  343. , m_output_stream(Core::Stream::MemoryStream::construct(m_stream_backing_buffer.bytes()).release_value_but_fixme_should_propagate_errors())
  344. , m_socket(move(socket))
  345. , m_job(HTTP::HttpsJob::construct(move(request), *m_output_stream))
  346. {
  347. m_job->on_headers_received = [weak_this = make_weak_ptr()](auto& response_headers, auto response_code) mutable {
  348. if (auto strong_this = weak_this.strong_ref()) {
  349. strong_this->m_response_code = response_code;
  350. for (auto& header : response_headers) {
  351. strong_this->m_response_headers.set(header.key, header.value);
  352. }
  353. }
  354. };
  355. m_job->on_finish = [weak_this = make_weak_ptr()](bool success) mutable {
  356. Core::deferred_invoke([weak_this, success]() mutable {
  357. if (auto strong_this = weak_this.strong_ref()) {
  358. ReadonlyBytes response_bytes { strong_this->m_output_stream->bytes().data(), strong_this->m_output_stream->offset() };
  359. auto response_buffer = ByteBuffer::copy(response_bytes).release_value_but_fixme_should_propagate_errors();
  360. strong_this->on_buffered_request_finish(success, strong_this->m_output_stream->offset(), strong_this->m_response_headers, strong_this->m_response_code, response_buffer);
  361. }
  362. });
  363. };
  364. m_job->start(*m_socket);
  365. }
  366. Optional<u32> m_response_code;
  367. ByteBuffer m_stream_backing_buffer;
  368. NonnullOwnPtr<Core::Stream::MemoryStream> m_output_stream;
  369. NonnullOwnPtr<Core::Stream::BufferedSocketBase> m_socket;
  370. NonnullRefPtr<HTTP::HttpsJob> m_job;
  371. HashMap<String, String, CaseInsensitiveStringTraits> m_response_headers;
  372. };
  373. class GeminiHeadlessRequest
  374. : public Web::ResourceLoaderConnectorRequest
  375. , public Weakable<GeminiHeadlessRequest> {
  376. public:
  377. static ErrorOr<NonnullRefPtr<GeminiHeadlessRequest>> create(String const&, AK::URL const& url, HashMap<String, String> const&, ReadonlyBytes, Core::ProxyData const&)
  378. {
  379. auto stream_backing_buffer = TRY(ByteBuffer::create_uninitialized(1 * MiB));
  380. auto underlying_socket = TRY(Core::Stream::TCPSocket::connect(url.host(), url.port().value_or(80)));
  381. TRY(underlying_socket->set_blocking(false));
  382. auto socket = TRY(Core::Stream::BufferedSocket<Core::Stream::TCPSocket>::create(move(underlying_socket)));
  383. Gemini::GeminiRequest request;
  384. request.set_url(url);
  385. return adopt_ref(*new GeminiHeadlessRequest(move(request), move(socket), move(stream_backing_buffer)));
  386. }
  387. virtual ~GeminiHeadlessRequest() override
  388. {
  389. }
  390. virtual void set_should_buffer_all_input(bool) override
  391. {
  392. }
  393. virtual bool stop() override
  394. {
  395. return false;
  396. }
  397. virtual void stream_into(Core::Stream::Stream&) override
  398. {
  399. }
  400. private:
  401. GeminiHeadlessRequest(Gemini::GeminiRequest&& request, NonnullOwnPtr<Core::Stream::BufferedSocketBase> socket, ByteBuffer&& stream_backing_buffer)
  402. : m_stream_backing_buffer(move(stream_backing_buffer))
  403. , m_output_stream(Core::Stream::MemoryStream::construct(m_stream_backing_buffer.bytes()).release_value_but_fixme_should_propagate_errors())
  404. , m_socket(move(socket))
  405. , m_job(Gemini::Job::construct(move(request), *m_output_stream))
  406. {
  407. m_job->on_headers_received = [weak_this = make_weak_ptr()](auto& response_headers, auto response_code) mutable {
  408. if (auto strong_this = weak_this.strong_ref()) {
  409. strong_this->m_response_code = response_code;
  410. for (auto& header : response_headers) {
  411. strong_this->m_response_headers.set(header.key, header.value);
  412. }
  413. }
  414. };
  415. m_job->on_finish = [weak_this = make_weak_ptr()](bool success) mutable {
  416. Core::deferred_invoke([weak_this, success]() mutable {
  417. if (auto strong_this = weak_this.strong_ref()) {
  418. ReadonlyBytes response_bytes { strong_this->m_output_stream->bytes().data(), strong_this->m_output_stream->offset() };
  419. auto response_buffer = ByteBuffer::copy(response_bytes).release_value_but_fixme_should_propagate_errors();
  420. strong_this->on_buffered_request_finish(success, strong_this->m_output_stream->offset(), strong_this->m_response_headers, strong_this->m_response_code, response_buffer);
  421. }
  422. });
  423. };
  424. m_job->start(*m_socket);
  425. }
  426. Optional<u32> m_response_code;
  427. ByteBuffer m_stream_backing_buffer;
  428. NonnullOwnPtr<Core::Stream::MemoryStream> m_output_stream;
  429. NonnullOwnPtr<Core::Stream::BufferedSocketBase> m_socket;
  430. NonnullRefPtr<Gemini::Job> m_job;
  431. HashMap<String, String, CaseInsensitiveStringTraits> m_response_headers;
  432. };
  433. static NonnullRefPtr<HeadlessRequestServer> create()
  434. {
  435. return adopt_ref(*new HeadlessRequestServer());
  436. }
  437. virtual ~HeadlessRequestServer() override { }
  438. virtual void prefetch_dns(AK::URL const&) override { }
  439. virtual void preconnect(AK::URL const&) override { }
  440. virtual RefPtr<Web::ResourceLoaderConnectorRequest> start_request(String const& method, AK::URL const& url, HashMap<String, String> const& request_headers, ReadonlyBytes request_body, Core::ProxyData const& proxy) override
  441. {
  442. RefPtr<Web::ResourceLoaderConnectorRequest> request;
  443. if (url.protocol().equals_ignoring_case("http"sv)) {
  444. auto request_or_error = HTTPHeadlessRequest::create(method, url, request_headers, request_body, proxy);
  445. if (request_or_error.is_error())
  446. return {};
  447. request = request_or_error.release_value();
  448. }
  449. if (url.protocol().equals_ignoring_case("https"sv)) {
  450. auto request_or_error = HTTPSHeadlessRequest::create(method, url, request_headers, request_body, proxy);
  451. if (request_or_error.is_error())
  452. return {};
  453. request = request_or_error.release_value();
  454. }
  455. if (url.protocol().equals_ignoring_case("gemini"sv)) {
  456. auto request_or_error = GeminiHeadlessRequest::create(method, url, request_headers, request_body, proxy);
  457. if (request_or_error.is_error())
  458. return {};
  459. request = request_or_error.release_value();
  460. }
  461. if (request)
  462. s_all_requests.set(request);
  463. return request;
  464. }
  465. private:
  466. HeadlessRequestServer() { }
  467. };
  468. class HeadlessWebSocketClientManager : public Web::WebSockets::WebSocketClientManager {
  469. public:
  470. class HeadlessWebSocket
  471. : public Web::WebSockets::WebSocketClientSocket
  472. , public Weakable<HeadlessWebSocket> {
  473. public:
  474. static NonnullRefPtr<HeadlessWebSocket> create(NonnullRefPtr<WebSocket::WebSocket> underlying_socket)
  475. {
  476. return adopt_ref(*new HeadlessWebSocket(move(underlying_socket)));
  477. }
  478. virtual ~HeadlessWebSocket() override
  479. {
  480. }
  481. virtual Web::WebSockets::WebSocket::ReadyState ready_state() override
  482. {
  483. switch (m_websocket->ready_state()) {
  484. case WebSocket::ReadyState::Connecting:
  485. return Web::WebSockets::WebSocket::ReadyState::Connecting;
  486. case WebSocket::ReadyState::Open:
  487. return Web::WebSockets::WebSocket::ReadyState::Open;
  488. case WebSocket::ReadyState::Closing:
  489. return Web::WebSockets::WebSocket::ReadyState::Closing;
  490. case WebSocket::ReadyState::Closed:
  491. return Web::WebSockets::WebSocket::ReadyState::Closed;
  492. }
  493. VERIFY_NOT_REACHED();
  494. }
  495. virtual void send(ByteBuffer binary_or_text_message, bool is_text) override
  496. {
  497. m_websocket->send(WebSocket::Message(binary_or_text_message, is_text));
  498. }
  499. virtual void send(StringView message) override
  500. {
  501. m_websocket->send(WebSocket::Message(message));
  502. }
  503. virtual void close(u16 code, String reason) override
  504. {
  505. m_websocket->close(code, reason);
  506. }
  507. private:
  508. HeadlessWebSocket(NonnullRefPtr<WebSocket::WebSocket> underlying_socket)
  509. : m_websocket(move(underlying_socket))
  510. {
  511. m_websocket->on_open = [weak_this = make_weak_ptr()] {
  512. if (auto strong_this = weak_this.strong_ref())
  513. if (strong_this->on_open)
  514. strong_this->on_open();
  515. };
  516. m_websocket->on_message = [weak_this = make_weak_ptr()](auto message) {
  517. if (auto strong_this = weak_this.strong_ref()) {
  518. if (strong_this->on_message) {
  519. strong_this->on_message(Web::WebSockets::WebSocketClientSocket::Message {
  520. .data = move(message.data()),
  521. .is_text = message.is_text(),
  522. });
  523. }
  524. }
  525. };
  526. m_websocket->on_error = [weak_this = make_weak_ptr()](auto error) {
  527. if (auto strong_this = weak_this.strong_ref()) {
  528. if (strong_this->on_error) {
  529. switch (error) {
  530. case WebSocket::WebSocket::Error::CouldNotEstablishConnection:
  531. strong_this->on_error(Web::WebSockets::WebSocketClientSocket::Error::CouldNotEstablishConnection);
  532. return;
  533. case WebSocket::WebSocket::Error::ConnectionUpgradeFailed:
  534. strong_this->on_error(Web::WebSockets::WebSocketClientSocket::Error::ConnectionUpgradeFailed);
  535. return;
  536. case WebSocket::WebSocket::Error::ServerClosedSocket:
  537. strong_this->on_error(Web::WebSockets::WebSocketClientSocket::Error::ServerClosedSocket);
  538. return;
  539. }
  540. VERIFY_NOT_REACHED();
  541. }
  542. }
  543. };
  544. m_websocket->on_close = [weak_this = make_weak_ptr()](u16 code, String reason, bool was_clean) {
  545. if (auto strong_this = weak_this.strong_ref())
  546. if (strong_this->on_close)
  547. strong_this->on_close(code, move(reason), was_clean);
  548. };
  549. }
  550. NonnullRefPtr<WebSocket::WebSocket> m_websocket;
  551. };
  552. static NonnullRefPtr<HeadlessWebSocketClientManager> create()
  553. {
  554. return adopt_ref(*new HeadlessWebSocketClientManager());
  555. }
  556. virtual ~HeadlessWebSocketClientManager() override { }
  557. virtual RefPtr<Web::WebSockets::WebSocketClientSocket> connect(AK::URL const& url, String const& origin) override
  558. {
  559. WebSocket::ConnectionInfo connection_info(url);
  560. connection_info.set_origin(origin);
  561. auto connection = HeadlessWebSocket::create(WebSocket::WebSocket::create(move(connection_info)));
  562. return connection;
  563. }
  564. private:
  565. HeadlessWebSocketClientManager() { }
  566. };
  567. ErrorOr<int> serenity_main(Main::Arguments arguments)
  568. {
  569. int take_screenshot_after = 1;
  570. StringView url;
  571. StringView resources_folder;
  572. StringView error_page_url;
  573. Core::EventLoop event_loop;
  574. Core::ArgsParser args_parser;
  575. args_parser.set_general_help("This utility runs the Browser in headless mode.");
  576. args_parser.add_option(take_screenshot_after, "Take a screenshot after [n] seconds (default: 1)", "screenshot", 's', "n");
  577. args_parser.add_option(resources_folder, "Path of the base resources folder (defaults to /res)", "resources", 'r', "resources-root-path");
  578. args_parser.add_option(error_page_url, "URL for the error page (defaults to file:///res/html/error.html)", "error-page", 'e', "error-page-url");
  579. args_parser.add_positional_argument(url, "URL to open", "url", Core::ArgsParser::Required::Yes);
  580. args_parser.parse(arguments);
  581. Web::ImageDecoding::Decoder::initialize(HeadlessImageDecoderClient::create());
  582. Web::ResourceLoader::initialize(HeadlessRequestServer::create());
  583. Web::WebSockets::WebSocketClientManager::initialize(HeadlessWebSocketClientManager::create());
  584. if (!resources_folder.is_empty()) {
  585. Web::FrameLoader::set_default_favicon_path(LexicalPath::join(resources_folder, "icons/16x16/app-browser.png"sv).string());
  586. Gfx::FontDatabase::set_default_fonts_lookup_path(LexicalPath::join(resources_folder, "fonts"sv).string());
  587. }
  588. Gfx::FontDatabase::set_default_font_query("Katica 10 400 0");
  589. Gfx::FontDatabase::set_window_title_font_query("Katica 10 700 0");
  590. Gfx::FontDatabase::set_fixed_width_font_query("Csilla 10 400 0");
  591. if (!error_page_url.is_empty())
  592. Web::FrameLoader::set_error_page_url(error_page_url);
  593. auto page_client = HeadlessBrowserPageClient::create();
  594. if (!resources_folder.is_empty())
  595. page_client->setup_palette(Gfx::load_system_theme(LexicalPath::join(resources_folder, "themes/Default.ini"sv).string()));
  596. else
  597. page_client->setup_palette(Gfx::load_system_theme("/res/themes/Default.ini"));
  598. dbgln("Loading {}", url);
  599. page_client->load(AK::URL(url));
  600. // FIXME: Allow passing these values as arguments
  601. page_client->set_viewport_rect({ 0, 0, 800, 600 });
  602. page_client->set_screen_rect({ 0, 0, 800, 600 });
  603. dbgln("Taking screenshot after {} seconds !", take_screenshot_after);
  604. auto timer = Core::Timer::create_single_shot(
  605. take_screenshot_after * 1000,
  606. [page_client = move(page_client)]() mutable {
  607. // FIXME: Allow passing the output path as argument
  608. String output_file_path = "output.png";
  609. dbgln("Saving to {}", output_file_path);
  610. if (Core::File::exists(output_file_path))
  611. [[maybe_unused]]
  612. auto ignored = Core::File::remove(output_file_path, Core::File::RecursionMode::Disallowed, true);
  613. auto output_file = MUST(Core::File::open(output_file_path, Core::OpenMode::WriteOnly));
  614. auto output_rect = page_client->screen_rect();
  615. auto output_bitmap = MUST(Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRx8888, output_rect.size()));
  616. page_client->paint(output_rect, output_bitmap);
  617. auto image_buffer = Gfx::PNGWriter::encode(output_bitmap);
  618. output_file->write(image_buffer.data(), image_buffer.size());
  619. exit(0);
  620. });
  621. timer->start();
  622. return event_loop.exec();
  623. }