Serenity Operating System
at hosted 333 lines 12 kB view raw
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 "BookmarksBarWidget.h" 28#include "History.h" 29#include "InspectorWidget.h" 30#include <LibCore/File.h> 31#include <LibGUI/AboutDialog.h> 32#include <LibGUI/Action.h> 33#include <LibGUI/Application.h> 34#include <LibGUI/BoxLayout.h> 35#include <LibGUI/Button.h> 36#include <LibGUI/Menu.h> 37#include <LibGUI/MenuBar.h> 38#include <LibGUI/StatusBar.h> 39#include <LibGUI/TextBox.h> 40#include <LibGUI/ToolBar.h> 41#include <LibGUI/Window.h> 42#include <LibWeb/CSS/StyleResolver.h> 43#include <LibWeb/DOM/Element.h> 44#include <LibWeb/DOMTreeModel.h> 45#include <LibWeb/Dump.h> 46#include <LibWeb/Frame.h> 47#include <LibWeb/HtmlView.h> 48#include <LibWeb/Layout/LayoutBlock.h> 49#include <LibWeb/Layout/LayoutDocument.h> 50#include <LibWeb/Layout/LayoutInline.h> 51#include <LibWeb/Layout/LayoutNode.h> 52#include <LibWeb/Parser/CSSParser.h> 53#include <LibWeb/Parser/HTMLParser.h> 54#include <LibWeb/ResourceLoader.h> 55#include <stdio.h> 56#include <stdlib.h> 57 58static const char* home_url = "file:///home/anon/www/welcome.html"; 59static const char* bookmarks_filename = "/home/anon/bookmarks.json"; 60 61static String s_title = ""; 62 63int main(int argc, char** argv) 64{ 65 if (getuid() == 0) { 66 fprintf(stderr, "Refusing to run as root\n"); 67 return 1; 68 } 69 70#ifdef __serenity__ 71 if (pledge("stdio shared_buffer accept unix cpath rpath wpath fattr", nullptr) < 0) { 72 perror("pledge"); 73 return 1; 74 } 75#endif 76 77 GUI::Application app(argc, argv); 78 79 // Connect to the ProtocolServer immediately so we can drop the "unix" pledge. 80 Web::ResourceLoader::the(); 81 82#ifdef __serenity__ 83 if (pledge("stdio shared_buffer accept cpath rpath wpath", nullptr) < 0) { 84 perror("pledge"); 85 return 1; 86 } 87 88 if (unveil("/home", "rwc") < 0) { 89 perror("unveil"); 90 return 1; 91 } 92 93 if (unveil("/res", "r") < 0) { 94 perror("unveil"); 95 return 1; 96 } 97 98 unveil(nullptr, nullptr); 99#endif 100 101 auto window = GUI::Window::construct(); 102 window->set_rect(100, 100, 640, 480); 103 104 auto& widget = window->set_main_widget<GUI::Widget>(); 105 widget.set_fill_with_background_color(true); 106 widget.set_layout<GUI::VerticalBoxLayout>(); 107 widget.layout()->set_spacing(0); 108 109 bool bookmarksbar_enabled = true; 110 111 auto& toolbar = widget.add<GUI::ToolBar>(); 112 auto& bookmarksbar = widget.add<BookmarksBarWidget>(bookmarks_filename, bookmarksbar_enabled); 113 auto& html_widget = widget.add<Web::HtmlView>(); 114 115 bookmarksbar.on_bookmark_click = [&](auto&, auto& url) { 116 html_widget.load(url); 117 }; 118 119 History<URL> history; 120 121 RefPtr<GUI::Action> go_back_action; 122 RefPtr<GUI::Action> go_forward_action; 123 124 auto update_actions = [&]() { 125 go_back_action->set_enabled(history.can_go_back()); 126 go_forward_action->set_enabled(history.can_go_forward()); 127 }; 128 129 bool should_push_loads_to_history = true; 130 131 go_back_action = GUI::CommonActions::make_go_back_action([&](auto&) { 132 history.go_back(); 133 update_actions(); 134 TemporaryChange<bool> change(should_push_loads_to_history, false); 135 html_widget.load(history.current()); 136 }); 137 138 go_forward_action = GUI::CommonActions::make_go_forward_action([&](auto&) { 139 history.go_forward(); 140 update_actions(); 141 TemporaryChange<bool> change(should_push_loads_to_history, false); 142 html_widget.load(history.current()); 143 }); 144 145 toolbar.add_action(*go_back_action); 146 toolbar.add_action(*go_forward_action); 147 148 toolbar.add_action(GUI::CommonActions::make_go_home_action([&](auto&) { 149 html_widget.load(home_url); 150 })); 151 152 toolbar.add_action(GUI::CommonActions::make_reload_action([&](auto&) { 153 TemporaryChange<bool> change(should_push_loads_to_history, false); 154 html_widget.reload(); 155 })); 156 157 auto& location_box = toolbar.add<GUI::TextBox>(); 158 159 location_box.on_return_pressed = [&] { 160 html_widget.load(location_box.text()); 161 }; 162 163 auto& bookmark_button = toolbar.add<GUI::Button>(); 164 bookmark_button.set_button_style(Gfx::ButtonStyle::CoolBar); 165 bookmark_button.set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/star-black.png")); 166 bookmark_button.set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fixed); 167 bookmark_button.set_preferred_size(22, 22); 168 169 auto update_bookmark_button = [&](const String& url) { 170 if (bookmarksbar.contains_bookmark(url)) { 171 bookmark_button.set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/star-yellow.png")); 172 } else { 173 bookmark_button.set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/star-contour.png")); 174 } 175 }; 176 177 bookmark_button.on_click = [&] { 178 auto url = html_widget.main_frame().document()->url().to_string(); 179 if (bookmarksbar.contains_bookmark(url)) { 180 bookmarksbar.remove_bookmark(url); 181 } else { 182 bookmarksbar.add_bookmark(url, s_title); 183 } 184 update_bookmark_button(url); 185 }; 186 187 html_widget.on_load_start = [&](auto& url) { 188 location_box.set_text(url.to_string()); 189 if (should_push_loads_to_history) 190 history.push(url); 191 update_actions(); 192 update_bookmark_button(url.to_string()); 193 }; 194 195 html_widget.on_link_click = [&](auto& url) { 196 if (url.starts_with("#")) { 197 html_widget.scroll_to_anchor(url.substring_view(1, url.length() - 1)); 198 } else { 199 html_widget.load(html_widget.document()->complete_url(url)); 200 } 201 }; 202 203 html_widget.on_title_change = [&](auto& title) { 204 s_title = title; 205 window->set_title(String::format("%s - Browser", title.characters())); 206 }; 207 208 auto focus_location_box_action = GUI::Action::create("Focus location box", { Mod_Ctrl, Key_L }, [&](auto&) { 209 location_box.select_all(); 210 location_box.set_focus(true); 211 }); 212 213 auto& statusbar = widget.add<GUI::StatusBar>(); 214 215 html_widget.on_link_hover = [&](auto& href) { 216 statusbar.set_text(href); 217 }; 218 219 bookmarksbar.on_bookmark_hover = [&](auto&, auto& url) { 220 statusbar.set_text(url); 221 }; 222 223 Web::ResourceLoader::the().on_load_counter_change = [&] { 224 if (Web::ResourceLoader::the().pending_loads() == 0) { 225 statusbar.set_text(""); 226 return; 227 } 228 statusbar.set_text(String::format("Loading (%d pending resources...)", Web::ResourceLoader::the().pending_loads())); 229 }; 230 231 auto menubar = make<GUI::MenuBar>(); 232 233 auto& app_menu = menubar->add_menu("Browser"); 234 app_menu.add_action(GUI::Action::create("Reload", { Mod_None, Key_F5 }, Gfx::Bitmap::load_from_file("/res/icons/16x16/reload.png"), [&](auto&) { 235 TemporaryChange<bool> change(should_push_loads_to_history, false); 236 html_widget.reload(); 237 })); 238 app_menu.add_separator(); 239 app_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) { 240 app.quit(); 241 })); 242 243 RefPtr<GUI::Window> dom_inspector_window; 244 245 auto& inspect_menu = menubar->add_menu("Inspect"); 246 inspect_menu.add_action(GUI::Action::create("View source", { Mod_Ctrl, Key_U }, [&](auto&) { 247 String filename_to_open; 248 char tmp_filename[] = "/tmp/view-source.XXXXXX"; 249 ASSERT(html_widget.document()); 250 if (html_widget.document()->url().protocol() == "file") { 251 filename_to_open = html_widget.document()->url().path(); 252 } else { 253 int fd = mkstemp(tmp_filename); 254 ASSERT(fd >= 0); 255 auto source = html_widget.document()->source(); 256 write(fd, source.characters(), source.length()); 257 close(fd); 258 filename_to_open = tmp_filename; 259 } 260 if (fork() == 0) { 261 execl("/bin/TextEditor", "TextEditor", filename_to_open.characters(), nullptr); 262 ASSERT_NOT_REACHED(); 263 } 264 })); 265 inspect_menu.add_action(GUI::Action::create("Inspect DOM tree", { Mod_None, Key_F12 }, [&](auto&) { 266 if (!dom_inspector_window) { 267 dom_inspector_window = GUI::Window::construct(); 268 dom_inspector_window->set_rect(100, 100, 300, 500); 269 dom_inspector_window->set_title("DOM inspector"); 270 dom_inspector_window->set_main_widget<InspectorWidget>(); 271 } 272 auto* inspector_widget = static_cast<InspectorWidget*>(dom_inspector_window->main_widget()); 273 inspector_widget->set_document(html_widget.document()); 274 dom_inspector_window->show(); 275 dom_inspector_window->move_to_front(); 276 })); 277 278 auto& debug_menu = menubar->add_menu("Debug"); 279 debug_menu.add_action(GUI::Action::create("Dump DOM tree", [&](auto&) { 280 dump_tree(*html_widget.document()); 281 })); 282 debug_menu.add_action(GUI::Action::create("Dump Layout tree", [&](auto&) { 283 dump_tree(*html_widget.document()->layout_node()); 284 })); 285 debug_menu.add_action(GUI::Action::create("Dump Style sheets", [&](auto&) { 286 for (auto& sheet : html_widget.document()->stylesheets()) { 287 dump_sheet(sheet); 288 } 289 })); 290 debug_menu.add_separator(); 291 auto line_box_borders_action = GUI::Action::create("Line box borders", [&](auto& action) { 292 action.set_checked(!action.is_checked()); 293 html_widget.set_should_show_line_box_borders(action.is_checked()); 294 html_widget.update(); 295 }); 296 line_box_borders_action->set_checkable(true); 297 line_box_borders_action->set_checked(false); 298 debug_menu.add_action(line_box_borders_action); 299 300 auto& bookmarks_menu = menubar->add_menu("Bookmarks"); 301 auto show_bookmarksbar_action = GUI::Action::create("Show bookmarks bar", [&](auto& action) { 302 action.set_checked(!action.is_checked()); 303 bookmarksbar.set_visible(action.is_checked()); 304 bookmarksbar.update(); 305 }); 306 show_bookmarksbar_action->set_checkable(true); 307 show_bookmarksbar_action->set_checked(bookmarksbar_enabled); 308 bookmarks_menu.add_action(show_bookmarksbar_action); 309 310 auto& help_menu = menubar->add_menu("Help"); 311 help_menu.add_action(GUI::Action::create("About", [&](const GUI::Action&) { 312 GUI::AboutDialog::show("Browser", Gfx::Bitmap::load_from_file("/res/icons/32x32/filetype-html.png"), window); 313 })); 314 315 app.set_menubar(move(menubar)); 316 317 window->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-html.png")); 318 319 window->set_title("Browser"); 320 window->show(); 321 322 URL url_to_load = home_url; 323 324 if (app.args().size() >= 1) { 325 url_to_load = URL(); 326 url_to_load.set_protocol("file"); 327 url_to_load.set_path(app.args()[0]); 328 } 329 330 html_widget.load(url_to_load); 331 332 return app.exec(); 333}