DocumentLoading.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. /*
  2. * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <AK/Debug.h>
  8. #include <AK/LexicalPath.h>
  9. #include <LibGemini/Document.h>
  10. #include <LibGfx/ImageFormats/ImageDecoder.h>
  11. #include <LibMarkdown/Document.h>
  12. #include <LibTextCodec/Decoder.h>
  13. #include <LibWeb/DOM/Document.h>
  14. #include <LibWeb/DOM/DocumentLoading.h>
  15. #include <LibWeb/HTML/Navigable.h>
  16. #include <LibWeb/HTML/NavigationParams.h>
  17. #include <LibWeb/HTML/Parser/HTMLEncodingDetection.h>
  18. #include <LibWeb/HTML/Parser/HTMLParser.h>
  19. #include <LibWeb/Namespace.h>
  20. #include <LibWeb/Platform/ImageCodecPlugin.h>
  21. #include <LibWeb/XML/XMLDocumentBuilder.h>
  22. namespace Web {
  23. static bool build_markdown_document(DOM::Document& document, ByteBuffer const& data)
  24. {
  25. auto markdown_document = Markdown::Document::parse(data);
  26. if (!markdown_document)
  27. return false;
  28. auto extra_head_contents = R"~~~(
  29. <style>
  30. .zoomable {
  31. cursor: zoom-in;
  32. max-width: 100%;
  33. }
  34. .zoomable.zoomed-in {
  35. cursor: zoom-out;
  36. max-width: none;
  37. }
  38. </style>
  39. <script>
  40. function imageClickEventListener(event) {
  41. let image = event.target;
  42. if (image.classList.contains("zoomable")) {
  43. image.classList.toggle("zoomed-in");
  44. }
  45. }
  46. function processImages() {
  47. let images = document.querySelectorAll("img");
  48. let windowWidth = window.innerWidth;
  49. images.forEach((image) => {
  50. if (image.naturalWidth > windowWidth) {
  51. image.classList.add("zoomable");
  52. } else {
  53. image.classList.remove("zoomable");
  54. image.classList.remove("zoomed-in");
  55. }
  56. image.addEventListener("click", imageClickEventListener);
  57. });
  58. }
  59. document.addEventListener("load", () => {
  60. processImages();
  61. });
  62. window.addEventListener("resize", () => {
  63. processImages();
  64. });
  65. </script>
  66. )~~~"sv;
  67. auto parser = HTML::HTMLParser::create(document, markdown_document->render_to_html(extra_head_contents), "utf-8");
  68. parser->run(document.url());
  69. return true;
  70. }
  71. static bool build_text_document(DOM::Document& document, ByteBuffer const& data)
  72. {
  73. auto html_element = DOM::create_element(document, HTML::TagNames::html, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  74. MUST(document.append_child(html_element));
  75. auto head_element = DOM::create_element(document, HTML::TagNames::head, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  76. MUST(html_element->append_child(head_element));
  77. auto title_element = DOM::create_element(document, HTML::TagNames::title, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  78. MUST(head_element->append_child(title_element));
  79. auto title_text = document.create_text_node(document.url().basename());
  80. MUST(title_element->append_child(title_text));
  81. auto body_element = DOM::create_element(document, HTML::TagNames::body, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  82. MUST(html_element->append_child(body_element));
  83. auto pre_element = DOM::create_element(document, HTML::TagNames::pre, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  84. MUST(body_element->append_child(pre_element));
  85. MUST(pre_element->append_child(document.create_text_node(DeprecatedString::copy(data))));
  86. return true;
  87. }
  88. static bool build_image_document(DOM::Document& document, ByteBuffer const& data)
  89. {
  90. auto image = Platform::ImageCodecPlugin::the().decode_image(data);
  91. if (!image.has_value() || image->frames.is_empty())
  92. return false;
  93. auto const& frame = image->frames[0];
  94. auto const& bitmap = frame.bitmap;
  95. if (!bitmap)
  96. return false;
  97. auto html_element = DOM::create_element(document, HTML::TagNames::html, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  98. MUST(document.append_child(html_element));
  99. auto head_element = DOM::create_element(document, HTML::TagNames::head, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  100. MUST(html_element->append_child(head_element));
  101. auto title_element = DOM::create_element(document, HTML::TagNames::title, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  102. MUST(head_element->append_child(title_element));
  103. auto basename = LexicalPath::basename(document.url().serialize_path());
  104. auto title_text = document.heap().allocate<DOM::Text>(document.realm(), document, MUST(String::formatted("{} [{}x{}]", basename, bitmap->width(), bitmap->height())));
  105. MUST(title_element->append_child(*title_text));
  106. auto body_element = DOM::create_element(document, HTML::TagNames::body, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  107. MUST(html_element->append_child(body_element));
  108. auto image_element = DOM::create_element(document, HTML::TagNames::img, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  109. MUST(image_element->set_attribute(HTML::AttributeNames::src, document.url().to_deprecated_string()));
  110. MUST(body_element->append_child(image_element));
  111. return true;
  112. }
  113. static bool build_gemini_document(DOM::Document& document, ByteBuffer const& data)
  114. {
  115. StringView gemini_data { data };
  116. auto gemini_document = Gemini::Document::parse(gemini_data, document.url());
  117. DeprecatedString html_data = gemini_document->render_to_html();
  118. dbgln_if(GEMINI_DEBUG, "Gemini data:\n\"\"\"{}\"\"\"", gemini_data);
  119. dbgln_if(GEMINI_DEBUG, "Converted to HTML:\n\"\"\"{}\"\"\"", html_data);
  120. auto parser = HTML::HTMLParser::create(document, html_data, "utf-8");
  121. parser->run(document.url());
  122. return true;
  123. }
  124. static bool build_xml_document(DOM::Document& document, ByteBuffer const& data)
  125. {
  126. auto encoding = HTML::run_encoding_sniffing_algorithm(document, data);
  127. auto decoder = TextCodec::decoder_for(encoding);
  128. VERIFY(decoder.has_value());
  129. auto source = decoder->to_utf8(data).release_value_but_fixme_should_propagate_errors();
  130. XML::Parser parser(source, { .resolve_external_resource = resolve_xml_resource });
  131. XMLDocumentBuilder builder { document };
  132. auto result = parser.parse_with_listener(builder);
  133. return !result.is_error() && !builder.has_error();
  134. }
  135. static bool build_video_document(DOM::Document& document)
  136. {
  137. auto html_element = DOM::create_element(document, HTML::TagNames::html, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  138. MUST(document.append_child(html_element));
  139. auto head_element = DOM::create_element(document, HTML::TagNames::head, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  140. MUST(html_element->append_child(head_element));
  141. auto body_element = DOM::create_element(document, HTML::TagNames::body, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  142. MUST(html_element->append_child(body_element));
  143. auto video_element = DOM::create_element(document, HTML::TagNames::video, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  144. MUST(video_element->set_attribute(HTML::AttributeNames::src, document.url().to_deprecated_string()));
  145. MUST(video_element->set_attribute(HTML::AttributeNames::autoplay, DeprecatedString::empty()));
  146. MUST(video_element->set_attribute(HTML::AttributeNames::controls, DeprecatedString::empty()));
  147. MUST(body_element->append_child(video_element));
  148. return true;
  149. }
  150. static bool build_audio_document(DOM::Document& document)
  151. {
  152. auto html_element = DOM::create_element(document, HTML::TagNames::html, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  153. MUST(document.append_child(html_element));
  154. auto head_element = DOM::create_element(document, HTML::TagNames::head, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  155. MUST(html_element->append_child(head_element));
  156. auto body_element = DOM::create_element(document, HTML::TagNames::body, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  157. MUST(html_element->append_child(body_element));
  158. auto video_element = DOM::create_element(document, HTML::TagNames::audio, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  159. MUST(video_element->set_attribute(HTML::AttributeNames::src, document.url().to_deprecated_string()));
  160. MUST(video_element->set_attribute(HTML::AttributeNames::autoplay, DeprecatedString::empty()));
  161. MUST(video_element->set_attribute(HTML::AttributeNames::controls, DeprecatedString::empty()));
  162. MUST(body_element->append_child(video_element));
  163. return true;
  164. }
  165. bool parse_document(DOM::Document& document, ByteBuffer const& data)
  166. {
  167. auto& mime_type = document.content_type();
  168. if (mime_type == "text/html") {
  169. auto parser = HTML::HTMLParser::create_with_uncertain_encoding(document, data);
  170. parser->run(document.url());
  171. return true;
  172. }
  173. if (mime_type.ends_with("+xml"sv) || mime_type.is_one_of("text/xml", "application/xml"))
  174. return build_xml_document(document, data);
  175. if (mime_type.starts_with("image/"sv))
  176. return build_image_document(document, data);
  177. if (mime_type.starts_with("video/"sv))
  178. return build_video_document(document);
  179. if (mime_type.starts_with("audio/"sv))
  180. return build_audio_document(document);
  181. if (mime_type == "text/plain" || mime_type == "application/json")
  182. return build_text_document(document, data);
  183. if (mime_type == "text/markdown")
  184. return build_markdown_document(document, data);
  185. if (mime_type == "text/gemini")
  186. return build_gemini_document(document, data);
  187. return false;
  188. }
  189. // https://html.spec.whatwg.org/multipage/browsing-the-web.html#loading-a-document
  190. JS::GCPtr<DOM::Document> load_document(Optional<HTML::NavigationParams> navigation_params)
  191. {
  192. VERIFY(navigation_params.has_value());
  193. auto document = DOM::Document::create_and_initialize(DOM::Document::Type::HTML, "text/html", *navigation_params).release_value_but_fixme_should_propagate_errors();
  194. auto& realm = document->realm();
  195. if (navigation_params->response->body()) {
  196. auto process_body = [navigation_params, document](ByteBuffer bytes) {
  197. if (!parse_document(*document, bytes)) {
  198. // FIXME: Load html page with an error if parsing failed.
  199. TODO();
  200. }
  201. };
  202. auto process_body_error = [](auto) {
  203. // FIXME: Load html page with an error if read of body failed.
  204. TODO();
  205. };
  206. navigation_params->response->body()->fully_read(
  207. realm,
  208. move(process_body),
  209. move(process_body_error),
  210. JS::NonnullGCPtr { realm.global_object() })
  211. .release_value_but_fixme_should_propagate_errors();
  212. }
  213. return document;
  214. }
  215. // https://html.spec.whatwg.org/multipage/document-lifecycle.html#read-ua-inline
  216. JS::GCPtr<DOM::Document> create_document_for_inline_content(JS::GCPtr<HTML::Navigable> navigable, Optional<String> navigation_id, StringView content_html)
  217. {
  218. auto& vm = navigable->vm();
  219. // 1. Let origin be a new opaque origin.
  220. HTML::Origin origin {};
  221. // 2. Let coop be a new cross-origin opener policy.
  222. auto coop = HTML::CrossOriginOpenerPolicy {};
  223. // 3. Let coopEnforcementResult be a new cross-origin opener policy enforcement result with
  224. // url: response's URL
  225. // origin: origin
  226. // cross-origin opener policy: coop
  227. HTML::CrossOriginOpenerPolicyEnforcementResult coop_enforcement_result {
  228. .url = AK::URL("about:error"), // AD-HOC
  229. .origin = origin,
  230. .cross_origin_opener_policy = coop
  231. };
  232. // 4. Let navigationParams be a new navigation params with
  233. // id: navigationId
  234. // request: null
  235. // response: a new response
  236. // origin: origin
  237. // policy container: a new policy container
  238. // final sandboxing flag set: an empty set
  239. // cross-origin opener policy: coop
  240. // COOP enforcement result: coopEnforcementResult
  241. // reserved environment: null
  242. // navigable: navigable
  243. // FIXME: navigation timing type: navTimingType
  244. // FIXME: fetch controller: fetch controller
  245. // FIXME: commit early hints: null
  246. auto response = Fetch::Infrastructure::Response::create(vm);
  247. response->url_list().append(AK::URL("about:error")); // AD-HOC: https://github.com/whatwg/html/issues/9122
  248. HTML::NavigationParams navigation_params {
  249. .id = navigation_id,
  250. .request = {},
  251. .response = *response,
  252. .origin = move(origin),
  253. .policy_container = HTML::PolicyContainer {},
  254. .final_sandboxing_flag_set = HTML::SandboxingFlagSet {},
  255. .cross_origin_opener_policy = move(coop),
  256. .coop_enforcement_result = move(coop_enforcement_result),
  257. .reserved_environment = {},
  258. .browsing_context = navigable->active_browsing_context(),
  259. .navigable = navigable,
  260. };
  261. // 5. Let document be the result of creating and initializing a Document object given "html", "text/html", and navigationParams.
  262. auto document = DOM::Document::create_and_initialize(DOM::Document::Type::HTML, "text/html", navigation_params).release_value_but_fixme_should_propagate_errors();
  263. // 6. Either associate document with a custom rendering that is not rendered using the normal Document rendering rules, or mutate document until it represents the content the
  264. // user agent wants to render.
  265. auto parser = HTML::HTMLParser::create(document, content_html, "utf-8");
  266. parser->run(AK::URL("about:error"));
  267. // 7. Return document.
  268. return document;
  269. }
  270. }