Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "History.h"
28#include "InspectorWidget.h"
29#include <LibCore/File.h>
30#include <LibGUI/AboutDialog.h>
31#include <LibGUI/Action.h>
32#include <LibGUI/Application.h>
33#include <LibGUI/BoxLayout.h>
34#include <LibGUI/Menu.h>
35#include <LibGUI/MenuBar.h>
36#include <LibGUI/StatusBar.h>
37#include <LibGUI/TextBox.h>
38#include <LibGUI/ToolBar.h>
39#include <LibGUI/Window.h>
40#include <LibHTML/CSS/StyleResolver.h>
41#include <LibHTML/DOM/Element.h>
42#include <LibHTML/DOMTreeModel.h>
43#include <LibHTML/Dump.h>
44#include <LibHTML/HtmlView.h>
45#include <LibHTML/Layout/LayoutBlock.h>
46#include <LibHTML/Layout/LayoutDocument.h>
47#include <LibHTML/Layout/LayoutInline.h>
48#include <LibHTML/Layout/LayoutNode.h>
49#include <LibHTML/Parser/CSSParser.h>
50#include <LibHTML/Parser/HTMLParser.h>
51#include <LibHTML/ResourceLoader.h>
52#include <stdio.h>
53#include <stdlib.h>
54
55static const char* home_url = "file:///home/anon/www/welcome.html";
56
57int main(int argc, char** argv)
58{
59 if (pledge("stdio shared_buffer accept unix cpath rpath fattr", nullptr) < 0) {
60 perror("pledge");
61 return 1;
62 }
63
64 GUI::Application app(argc, argv);
65
66 // Connect to the ProtocolServer immediately so we can drop the "unix" pledge.
67 ResourceLoader::the();
68
69 if (pledge("stdio shared_buffer accept rpath", nullptr) < 0) {
70 perror("pledge");
71 return 1;
72 }
73
74
75 auto window = GUI::Window::construct();
76 window->set_rect(100, 100, 640, 480);
77
78 auto widget = GUI::Widget::construct();
79 widget->set_fill_with_background_color(true);
80 widget->set_layout(make<GUI::VerticalBoxLayout>());
81 widget->layout()->set_spacing(0);
82
83 auto toolbar = widget->add<GUI::ToolBar>();
84 auto html_widget = widget->add<HtmlView>();
85
86 History<URL> history;
87
88 RefPtr<GUI::Action> go_back_action;
89 RefPtr<GUI::Action> go_forward_action;
90
91 auto update_actions = [&]() {
92 go_back_action->set_enabled(history.can_go_back());
93 go_forward_action->set_enabled(history.can_go_forward());
94 };
95
96 bool should_push_loads_to_history = true;
97
98 go_back_action = GUI::CommonActions::make_go_back_action([&](auto&) {
99 history.go_back();
100 update_actions();
101 TemporaryChange<bool> change(should_push_loads_to_history, false);
102 html_widget->load(history.current());
103 });
104
105 go_forward_action = GUI::CommonActions::make_go_forward_action([&](auto&) {
106 history.go_forward();
107 update_actions();
108 TemporaryChange<bool> change(should_push_loads_to_history, false);
109 html_widget->load(history.current());
110 });
111
112 toolbar->add_action(*go_back_action);
113 toolbar->add_action(*go_forward_action);
114
115 toolbar->add_action(GUI::CommonActions::make_go_home_action([&](auto&) {
116 html_widget->load(home_url);
117 }));
118
119 toolbar->add_action(GUI::CommonActions::make_reload_action([&](auto&) {
120 TemporaryChange<bool> change(should_push_loads_to_history, false);
121 html_widget->reload();
122 }));
123
124 auto location_box = toolbar->add<GUI::TextBox>();
125
126 location_box->on_return_pressed = [&] {
127 html_widget->load(location_box->text());
128 };
129
130 html_widget->on_load_start = [&](auto& url) {
131 location_box->set_text(url.to_string());
132 if (should_push_loads_to_history)
133 history.push(url);
134 update_actions();
135 };
136
137 html_widget->on_link_click = [&](auto& url) {
138 if (url.starts_with("#")) {
139 html_widget->scroll_to_anchor(url.substring_view(1, url.length() - 1));
140 } else {
141 html_widget->load(html_widget->document()->complete_url(url));
142 }
143 };
144
145 html_widget->on_title_change = [&](auto& title) {
146 window->set_title(String::format("%s - Browser", title.characters()));
147 };
148
149 auto focus_location_box_action = GUI::Action::create("Focus location box", { Mod_Ctrl, Key_L }, [&](auto&) {
150 location_box->select_all();
151 location_box->set_focus(true);
152 });
153
154 auto statusbar = widget->add<GUI::StatusBar>();
155
156 html_widget->on_link_hover = [&](auto& href) {
157 statusbar->set_text(href);
158 };
159
160 ResourceLoader::the().on_load_counter_change = [&] {
161 if (ResourceLoader::the().pending_loads() == 0) {
162 statusbar->set_text("");
163 return;
164 }
165 statusbar->set_text(String::format("Loading (%d pending resources...)", ResourceLoader::the().pending_loads()));
166 };
167
168 auto menubar = make<GUI::MenuBar>();
169
170 auto app_menu = GUI::Menu::construct("Browser");
171 app_menu->add_action(GUI::CommonActions::make_quit_action([&](auto&) {
172 app.quit();
173 }));
174 menubar->add_menu(move(app_menu));
175
176 RefPtr<GUI::Window> dom_inspector_window;
177
178 auto inspect_menu = GUI::Menu::construct("Inspect");
179 inspect_menu->add_action(GUI::Action::create("View source", { Mod_Ctrl, Key_U }, [&](auto&) {
180 String filename_to_open;
181 char tmp_filename[] = "/tmp/view-source.XXXXXX";
182 ASSERT(html_widget->document());
183 if (html_widget->document()->url().protocol() == "file") {
184 filename_to_open = html_widget->document()->url().path();
185 } else {
186 int fd = mkstemp(tmp_filename);
187 ASSERT(fd >= 0);
188 auto source = html_widget->document()->source();
189 write(fd, source.characters(), source.length());
190 close(fd);
191 filename_to_open = tmp_filename;
192 }
193 if (fork() == 0) {
194 execl("/bin/TextEditor", "TextEditor", filename_to_open.characters(), nullptr);
195 ASSERT_NOT_REACHED();
196 }
197 }));
198 inspect_menu->add_action(GUI::Action::create("Inspect DOM tree", { Mod_None, Key_F12 }, [&](auto&) {
199 if (!dom_inspector_window) {
200 dom_inspector_window = GUI::Window::construct();
201 dom_inspector_window->set_rect(100, 100, 300, 500);
202 dom_inspector_window->set_title("DOM inspector");
203 auto dom_inspector_widget = InspectorWidget::construct();
204 dom_inspector_window->set_main_widget(dom_inspector_widget);
205 }
206 auto* inspector_widget = static_cast<InspectorWidget*>(dom_inspector_window->main_widget());
207 inspector_widget->set_document(html_widget->document());
208 dom_inspector_window->show();
209 dom_inspector_window->move_to_front();
210 }));
211 menubar->add_menu(move(inspect_menu));
212
213 auto debug_menu = GUI::Menu::construct("Debug");
214 debug_menu->add_action(GUI::Action::create("Dump DOM tree", [&](auto&) {
215 dump_tree(*html_widget->document());
216 }));
217 debug_menu->add_action(GUI::Action::create("Dump Layout tree", [&](auto&) {
218 dump_tree(*html_widget->document()->layout_node());
219 }));
220 debug_menu->add_action(GUI::Action::create("Dump Style sheets", [&](auto&) {
221 for (auto& sheet : html_widget->document()->stylesheets()) {
222 dump_sheet(sheet);
223 }
224 }));
225 debug_menu->add_separator();
226 auto line_box_borders_action = GUI::Action::create("Line box borders", [&](auto& action) {
227 action.set_checked(!action.is_checked());
228 html_widget->set_should_show_line_box_borders(action.is_checked());
229 html_widget->update();
230 });
231 line_box_borders_action->set_checkable(true);
232 line_box_borders_action->set_checked(false);
233 debug_menu->add_action(line_box_borders_action);
234 menubar->add_menu(move(debug_menu));
235
236 auto help_menu = GUI::Menu::construct("Help");
237 help_menu->add_action(GUI::Action::create("About", [&](const GUI::Action&) {
238 GUI::AboutDialog::show("Browser", Gfx::Bitmap::load_from_file("/res/icons/32x32/filetype-html.png"), window);
239 }));
240 menubar->add_menu(move(help_menu));
241
242 app.set_menubar(move(menubar));
243
244 window->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-html.png"));
245
246 window->set_title("Browser");
247 window->set_main_widget(widget);
248 window->show();
249
250 URL url_to_load = home_url;
251
252 if (app.args().size() >= 1) {
253 url_to_load = URL();
254 url_to_load.set_protocol("file");
255 url_to_load.set_path(app.args()[0]);
256 }
257
258 html_widget->load(url_to_load);
259
260 return app.exec();
261}