Serenity Operating System
at portability 261 lines 9.6 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 "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}