headless-browser.cpp 29 KB

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