HtmlView.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. #include <AK/FileSystemPath.h>
  2. #include <LibCore/CFile.h>
  3. #include <LibDraw/PNGLoader.h>
  4. #include <LibGUI/GApplication.h>
  5. #include <LibGUI/GPainter.h>
  6. #include <LibGUI/GScrollBar.h>
  7. #include <LibGUI/GWindow.h>
  8. #include <LibHTML/DOM/Element.h>
  9. #include <LibHTML/DOM/ElementFactory.h>
  10. #include <LibHTML/DOM/HTMLAnchorElement.h>
  11. #include <LibHTML/DOM/HTMLImageElement.h>
  12. #include <LibHTML/DOM/Text.h>
  13. #include <LibHTML/Dump.h>
  14. #include <LibHTML/Frame.h>
  15. #include <LibHTML/HtmlView.h>
  16. #include <LibHTML/Layout/LayoutDocument.h>
  17. #include <LibHTML/Layout/LayoutNode.h>
  18. #include <LibHTML/Parser/HTMLParser.h>
  19. #include <LibHTML/RenderingContext.h>
  20. #include <LibHTML/ResourceLoader.h>
  21. #include <stdio.h>
  22. HtmlView::HtmlView(GWidget* parent)
  23. : GScrollableWidget(parent)
  24. , m_main_frame(Frame::create(*this))
  25. {
  26. main_frame().on_set_needs_display = [this](auto& content_rect) {
  27. if (content_rect.is_empty()) {
  28. update();
  29. return;
  30. }
  31. Rect adjusted_rect = content_rect;
  32. adjusted_rect.set_location(to_widget_position(content_rect.location()));
  33. update(adjusted_rect);
  34. };
  35. set_frame_shape(FrameShape::Container);
  36. set_frame_shadow(FrameShadow::Sunken);
  37. set_frame_thickness(2);
  38. set_should_hide_unnecessary_scrollbars(true);
  39. set_background_color(Color::White);
  40. }
  41. HtmlView::~HtmlView()
  42. {
  43. }
  44. void HtmlView::set_document(Document* new_document)
  45. {
  46. RefPtr<Document> old_document = document();
  47. if (new_document == old_document)
  48. return;
  49. if (old_document)
  50. old_document->on_layout_updated = nullptr;
  51. main_frame().set_document(new_document);
  52. if (new_document) {
  53. new_document->on_layout_updated = [this] {
  54. layout_and_sync_size();
  55. update();
  56. };
  57. }
  58. #ifdef HTML_DEBUG
  59. if (document != nullptr) {
  60. dbgprintf("\033[33;1mLayout tree before layout:\033[0m\n");
  61. ::dump_tree(*layout_root());
  62. }
  63. #endif
  64. layout_and_sync_size();
  65. update();
  66. }
  67. void HtmlView::layout_and_sync_size()
  68. {
  69. if (!document())
  70. return;
  71. bool had_vertical_scrollbar = vertical_scrollbar().is_visible();
  72. bool had_horizontal_scrollbar = horizontal_scrollbar().is_visible();
  73. main_frame().set_size(available_size());
  74. document()->layout();
  75. set_content_size(enclosing_int_rect(layout_root()->rect()).size());
  76. // NOTE: If layout caused us to gain or lose scrollbars, we have to lay out again
  77. // since the scrollbars now take up some of the available space.
  78. if (had_vertical_scrollbar != vertical_scrollbar().is_visible() || had_horizontal_scrollbar != horizontal_scrollbar().is_visible()) {
  79. main_frame().set_size(available_size());
  80. document()->layout();
  81. set_content_size(enclosing_int_rect(layout_root()->rect()).size());
  82. }
  83. main_frame().set_viewport_rect(visible_content_rect());
  84. #ifdef HTML_DEBUG
  85. dbgprintf("\033[33;1mLayout tree after layout:\033[0m\n");
  86. ::dump_tree(*layout_root());
  87. #endif
  88. }
  89. void HtmlView::resize_event(GResizeEvent& event)
  90. {
  91. GScrollableWidget::resize_event(event);
  92. layout_and_sync_size();
  93. }
  94. void HtmlView::paint_event(GPaintEvent& event)
  95. {
  96. GFrame::paint_event(event);
  97. GPainter painter(*this);
  98. painter.add_clip_rect(widget_inner_rect());
  99. painter.add_clip_rect(event.rect());
  100. if (!layout_root()) {
  101. painter.fill_rect(event.rect(), palette().color(background_role()));
  102. return;
  103. }
  104. painter.fill_rect(event.rect(), document()->background_color());
  105. if (auto background_bitmap = document()->background_image()) {
  106. painter.draw_tiled_bitmap(event.rect(), *background_bitmap);
  107. }
  108. painter.translate(frame_thickness(), frame_thickness());
  109. painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value());
  110. RenderingContext context(painter, palette());
  111. context.set_should_show_line_box_borders(m_should_show_line_box_borders);
  112. context.set_viewport_rect(visible_content_rect());
  113. layout_root()->render(context);
  114. }
  115. void HtmlView::mousemove_event(GMouseEvent& event)
  116. {
  117. if (!layout_root())
  118. return GScrollableWidget::mousemove_event(event);
  119. bool hovered_node_changed = false;
  120. bool is_hovering_link = false;
  121. bool was_hovering_link = document()->hovered_node() && document()->hovered_node()->is_link();
  122. auto result = layout_root()->hit_test(to_content_position(event.position()));
  123. const HTMLAnchorElement* hovered_link_element = nullptr;
  124. if (result.layout_node) {
  125. auto* node = result.layout_node->node();
  126. hovered_node_changed = node != document()->hovered_node();
  127. document()->set_hovered_node(const_cast<Node*>(node));
  128. if (node) {
  129. hovered_link_element = node->enclosing_link_element();
  130. if (hovered_link_element) {
  131. #ifdef HTML_DEBUG
  132. dbg() << "HtmlView: hovering over a link to " << hovered_link_element->href();
  133. #endif
  134. is_hovering_link = true;
  135. }
  136. }
  137. if (m_in_mouse_selection) {
  138. layout_root()->selection().set_end({ result.layout_node, result.index_in_node });
  139. dump_selection("MouseMove");
  140. update();
  141. }
  142. }
  143. if (window())
  144. window()->set_override_cursor(is_hovering_link ? GStandardCursor::Hand : GStandardCursor::None);
  145. if (hovered_node_changed) {
  146. update();
  147. auto* hovered_html_element = document()->hovered_node() ? document()->hovered_node()->enclosing_html_element() : nullptr;
  148. if (hovered_html_element && !hovered_html_element->title().is_null()) {
  149. auto screen_position = screen_relative_rect().location().translated(event.position());
  150. GApplication::the().show_tooltip(hovered_html_element->title(), screen_position.translated(4, 4));
  151. } else {
  152. GApplication::the().hide_tooltip();
  153. }
  154. }
  155. if (is_hovering_link != was_hovering_link) {
  156. if (on_link_hover) {
  157. on_link_hover(hovered_link_element ? document()->complete_url(hovered_link_element->href()).to_string() : String());
  158. }
  159. }
  160. event.accept();
  161. }
  162. void HtmlView::mousedown_event(GMouseEvent& event)
  163. {
  164. if (!layout_root())
  165. return GScrollableWidget::mousemove_event(event);
  166. bool hovered_node_changed = false;
  167. auto result = layout_root()->hit_test(to_content_position(event.position()));
  168. if (result.layout_node) {
  169. auto* node = result.layout_node->node();
  170. hovered_node_changed = node != document()->hovered_node();
  171. document()->set_hovered_node(const_cast<Node*>(node));
  172. if (node) {
  173. if (auto* link = node->enclosing_link_element()) {
  174. dbg() << "HtmlView: clicking on a link to " << link->href();
  175. if (on_link_click)
  176. on_link_click(link->href());
  177. } else {
  178. if (event.button() == GMouseButton::Left) {
  179. layout_root()->selection().set({ result.layout_node, result.index_in_node }, {});
  180. dump_selection("MouseDown");
  181. m_in_mouse_selection = true;
  182. }
  183. }
  184. }
  185. }
  186. if (hovered_node_changed)
  187. update();
  188. event.accept();
  189. }
  190. void HtmlView::mouseup_event(GMouseEvent& event)
  191. {
  192. if (!layout_root())
  193. return GScrollableWidget::mouseup_event(event);
  194. if (event.button() == GMouseButton::Left) {
  195. dump_selection("MouseUp");
  196. m_in_mouse_selection = false;
  197. }
  198. }
  199. void HtmlView::keydown_event(GKeyEvent& event)
  200. {
  201. if (event.modifiers() == 0) {
  202. switch (event.key()) {
  203. case Key_Home:
  204. vertical_scrollbar().set_value(0);
  205. break;
  206. case Key_End:
  207. vertical_scrollbar().set_value(vertical_scrollbar().max());
  208. break;
  209. case Key_Down:
  210. vertical_scrollbar().set_value(vertical_scrollbar().value() + vertical_scrollbar().step());
  211. break;
  212. case Key_Up:
  213. vertical_scrollbar().set_value(vertical_scrollbar().value() - vertical_scrollbar().step());
  214. break;
  215. case Key_Left:
  216. horizontal_scrollbar().set_value(horizontal_scrollbar().value() + horizontal_scrollbar().step());
  217. break;
  218. case Key_Right:
  219. horizontal_scrollbar().set_value(horizontal_scrollbar().value() - horizontal_scrollbar().step());
  220. break;
  221. case Key_PageDown:
  222. vertical_scrollbar().set_value(vertical_scrollbar().value() + frame_inner_rect().height());
  223. break;
  224. case Key_PageUp:
  225. vertical_scrollbar().set_value(vertical_scrollbar().value() - frame_inner_rect().height());
  226. break;
  227. }
  228. }
  229. event.accept();
  230. }
  231. void HtmlView::reload()
  232. {
  233. load(main_frame().document()->url());
  234. }
  235. static RefPtr<Document> create_image_document(const ByteBuffer& data, const URL& url)
  236. {
  237. auto document = adopt(*new Document);
  238. document->set_url(url);
  239. auto bitmap = load_png_from_memory(data.data(), data.size());
  240. ASSERT(bitmap);
  241. auto html_element = create_element(document, "html");
  242. document->append_child(html_element);
  243. auto head_element = create_element(document, "head");
  244. html_element->append_child(head_element);
  245. auto title_element = create_element(document, "title");
  246. head_element->append_child(title_element);
  247. auto basename = FileSystemPath(url.path()).basename();
  248. auto title_text = adopt(*new Text(document, String::format("%s [%dx%d]", basename.characters(), bitmap->width(), bitmap->height())));
  249. title_element->append_child(title_text);
  250. auto body_element = create_element(document, "body");
  251. html_element->append_child(body_element);
  252. auto image_element = create_element(document, "img");
  253. image_element->set_attribute("src", url.to_string());
  254. body_element->append_child(image_element);
  255. return document;
  256. }
  257. void HtmlView::load(const URL& url)
  258. {
  259. dbg() << "HtmlView::load: " << url;
  260. if (window())
  261. window()->set_override_cursor(GStandardCursor::None);
  262. if (on_load_start)
  263. on_load_start(url);
  264. ResourceLoader::the().load(url, [=](auto data) {
  265. if (data.is_null()) {
  266. dbg() << "Load failed!";
  267. ASSERT_NOT_REACHED();
  268. }
  269. RefPtr<Document> document;
  270. if (url.path().ends_with(".png")) {
  271. document = create_image_document(data, url);
  272. } else {
  273. document = parse_html_document(data, url);
  274. }
  275. ASSERT(document);
  276. set_document(document);
  277. if (on_title_change)
  278. on_title_change(document->title());
  279. });
  280. }
  281. const LayoutDocument* HtmlView::layout_root() const
  282. {
  283. return document() ? document()->layout_node() : nullptr;
  284. }
  285. LayoutDocument* HtmlView::layout_root()
  286. {
  287. if (!document())
  288. return nullptr;
  289. return const_cast<LayoutDocument*>(document()->layout_node());
  290. }
  291. void HtmlView::scroll_to_anchor(const StringView& name)
  292. {
  293. if (!document())
  294. return;
  295. auto* element = document()->get_element_by_id(name);
  296. if (!element) {
  297. auto candidates = document()->get_elements_by_name(name);
  298. for (auto* candidate : candidates) {
  299. if (is<HTMLAnchorElement>(*candidate)) {
  300. element = to<HTMLAnchorElement>(candidate);
  301. break;
  302. }
  303. }
  304. }
  305. if (!element) {
  306. dbg() << "HtmlView::scroll_to_anchor(): Anchor not found: '" << name << "'";
  307. return;
  308. }
  309. if (!element->layout_node()) {
  310. dbg() << "HtmlView::scroll_to_anchor(): Anchor found but without layout node: '" << name << "'";
  311. return;
  312. }
  313. auto& layout_node = *element->layout_node();
  314. FloatRect float_rect { layout_node.box_type_agnostic_position(), { (float)visible_content_rect().width(), (float)visible_content_rect().height() } };
  315. scroll_into_view(enclosing_int_rect(float_rect), true, true);
  316. window()->set_override_cursor(GStandardCursor::None);
  317. }
  318. Document* HtmlView::document()
  319. {
  320. return main_frame().document();
  321. }
  322. const Document* HtmlView::document() const
  323. {
  324. return main_frame().document();
  325. }
  326. void HtmlView::dump_selection(const char* event_name)
  327. {
  328. dbg() << event_name << " selection start: "
  329. << layout_root()->selection().start().layout_node << ":" << layout_root()->selection().start().index_in_node << ", end: "
  330. << layout_root()->selection().end().layout_node << ":" << layout_root()->selection().end().index_in_node;
  331. }
  332. void HtmlView::did_scroll()
  333. {
  334. main_frame().set_viewport_rect(visible_content_rect());
  335. }