1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-28 09:07:44 +00:00

Applications: Move to Userland/Applications/

This commit is contained in:
Andreas Kling 2021-01-12 12:05:23 +01:00
parent aa939c4b4b
commit dc28c07fa5
287 changed files with 1 additions and 1 deletions

View file

@ -0,0 +1,241 @@
/*
* Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "BookmarksBarWidget.h"
#include <LibGUI/Action.h>
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Button.h>
#include <LibGUI/Event.h>
#include <LibGUI/JsonArrayModel.h>
#include <LibGUI/Menu.h>
#include <LibGUI/Model.h>
#include <LibGUI/Widget.h>
#include <LibGUI/Window.h>
#include <LibGfx/Palette.h>
namespace Browser {
static BookmarksBarWidget* s_the;
BookmarksBarWidget& BookmarksBarWidget::the()
{
return *s_the;
}
BookmarksBarWidget::BookmarksBarWidget(const String& bookmarks_file, bool enabled)
{
s_the = this;
set_layout<GUI::HorizontalBoxLayout>();
layout()->set_spacing(0);
set_fixed_height(20);
if (!enabled)
set_visible(false);
m_additional = GUI::Button::construct();
m_additional->set_button_style(Gfx::ButtonStyle::CoolBar);
m_additional->set_text(">");
m_additional->set_fixed_size(14, 20);
m_additional->set_focus_policy(GUI::FocusPolicy::TabFocus);
m_additional->on_click = [this](auto) {
if (m_additional_menu) {
m_additional_menu->popup(m_additional->relative_position().translated(relative_position().translated(m_additional->window()->position())));
}
};
m_separator = GUI::Widget::construct();
m_context_menu = GUI::Menu::construct();
auto default_action = GUI::Action::create("Open", [this](auto&) {
if (on_bookmark_click)
on_bookmark_click(m_context_menu_url, Mod_None);
});
m_context_menu_default_action = default_action;
m_context_menu->add_action(default_action);
m_context_menu->add_action(GUI::Action::create("Open in new tab", [this](auto&) {
if (on_bookmark_click)
on_bookmark_click(m_context_menu_url, Mod_Ctrl);
}));
m_context_menu->add_action(GUI::Action::create("Delete", [this](auto&) {
remove_bookmark(m_context_menu_url);
}));
Vector<GUI::JsonArrayModel::FieldSpec> fields;
fields.empend("title", "Title", Gfx::TextAlignment::CenterLeft);
fields.empend("url", "Url", Gfx::TextAlignment::CenterRight);
set_model(GUI::JsonArrayModel::create(bookmarks_file, move(fields)));
model()->update();
}
BookmarksBarWidget::~BookmarksBarWidget()
{
if (m_model)
m_model->unregister_client(*this);
}
void BookmarksBarWidget::set_model(RefPtr<GUI::Model> model)
{
if (model == m_model)
return;
if (m_model)
m_model->unregister_client(*this);
m_model = move(model);
m_model->register_client(*this);
}
void BookmarksBarWidget::resize_event(GUI::ResizeEvent& event)
{
Widget::resize_event(event);
update_content_size();
}
void BookmarksBarWidget::model_did_update(unsigned)
{
remove_all_children();
m_bookmarks.clear();
int width = 0;
for (int item_index = 0; item_index < model()->row_count(); ++item_index) {
auto title = model()->index(item_index, 0).data().to_string();
auto url = model()->index(item_index, 1).data().to_string();
Gfx::IntRect rect { width, 0, font().width(title) + 32, height() };
auto& button = add<GUI::Button>();
m_bookmarks.append(button);
button.set_button_style(Gfx::ButtonStyle::CoolBar);
button.set_text(title);
button.set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-html.png"));
button.set_fixed_size(font().width(title) + 32, 20);
button.set_relative_rect(rect);
button.set_focus_policy(GUI::FocusPolicy::TabFocus);
button.set_tooltip(url);
button.on_click = [title, url, this](auto modifiers) {
if (on_bookmark_click)
on_bookmark_click(url, modifiers);
};
button.on_context_menu_request = [this, url](auto& context_menu_event) {
m_context_menu_url = url;
m_context_menu->popup(context_menu_event.screen_position(), m_context_menu_default_action);
};
width += rect.width();
}
add_child(*m_separator);
add_child(*m_additional);
update_content_size();
update();
}
void BookmarksBarWidget::update_content_size()
{
int x_position = 0;
m_last_visible_index = -1;
for (size_t i = 0; i < m_bookmarks.size(); ++i) {
auto& bookmark = m_bookmarks.at(i);
if (x_position + bookmark.width() > width()) {
m_last_visible_index = i;
break;
}
bookmark.set_x(x_position);
bookmark.set_visible(true);
x_position += bookmark.width();
}
if (m_last_visible_index < 0) {
m_additional->set_visible(false);
} else {
// hide all items > m_last_visible_index and create new bookmarks menu for them
m_additional->set_visible(true);
m_additional_menu = GUI::Menu::construct("Additional Bookmarks");
for (size_t i = m_last_visible_index; i < m_bookmarks.size(); ++i) {
auto& bookmark = m_bookmarks.at(i);
bookmark.set_visible(false);
m_additional_menu->add_action(GUI::Action::create(bookmark.text(),
Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-html.png"),
[&](auto&) {
bookmark.on_click(0);
}));
}
}
}
bool BookmarksBarWidget::contains_bookmark(const String& url)
{
for (int item_index = 0; item_index < model()->row_count(); ++item_index) {
auto item_title = model()->index(item_index, 0).data().to_string();
auto item_url = model()->index(item_index, 1).data().to_string();
if (item_url == url) {
return true;
}
}
return false;
}
bool BookmarksBarWidget::remove_bookmark(const String& url)
{
for (int item_index = 0; item_index < model()->row_count(); ++item_index) {
auto item_title = model()->index(item_index, 0).data().to_string();
auto item_url = model()->index(item_index, 1).data().to_string();
if (item_url == url) {
auto& json_model = *static_cast<GUI::JsonArrayModel*>(model());
const auto item_removed = json_model.remove(item_index);
if (item_removed)
json_model.store();
return item_removed;
}
}
return false;
}
bool BookmarksBarWidget::add_bookmark(const String& url, const String& title)
{
Vector<JsonValue> values;
values.append(title);
values.append(url);
auto& json_model = *static_cast<GUI::JsonArrayModel*>(model());
if (json_model.add(move(values))) {
json_model.store();
return true;
}
return false;
}
}

View file

@ -0,0 +1,81 @@
/*
* Copyright (c) 2020, Emanuel Sprung <emanuel.sprung@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <LibGUI/Forward.h>
#include <LibGUI/Model.h>
#include <LibGUI/Widget.h>
namespace Browser {
class BookmarksBarWidget final
: public GUI::Widget
, private GUI::ModelClient {
C_OBJECT(BookmarksBarWidget);
public:
static BookmarksBarWidget& the();
virtual ~BookmarksBarWidget() override;
void set_model(RefPtr<GUI::Model>);
GUI::Model* model() { return m_model.ptr(); }
const GUI::Model* model() const { return m_model.ptr(); }
Function<void(const String& url, unsigned modifiers)> on_bookmark_click;
Function<void(const String&, const String&)> on_bookmark_hover;
bool contains_bookmark(const String& url);
bool remove_bookmark(const String& url);
bool add_bookmark(const String& url, const String& title);
private:
BookmarksBarWidget(const String&, bool enabled);
// ^GUI::ModelClient
virtual void model_did_update(unsigned) override;
// ^GUI::Widget
virtual void resize_event(GUI::ResizeEvent&) override;
void update_content_size();
RefPtr<GUI::Model> m_model;
RefPtr<GUI::Button> m_additional;
RefPtr<GUI::Widget> m_separator;
RefPtr<GUI::Menu> m_additional_menu;
RefPtr<GUI::Menu> m_context_menu;
RefPtr<GUI::Action> m_context_menu_default_action;
String m_context_menu_url;
NonnullRefPtrVector<GUI::Button> m_bookmarks;
int m_last_visible_index { -1 };
};
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/String.h>
namespace Browser {
extern String g_home_url;
}

View file

@ -0,0 +1,130 @@
/*
* Copyright (c) 2020, Hunter Salyer <thefalsehonesty@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "BrowserConsoleClient.h"
#include "ConsoleWidget.h"
#include <AK/StringBuilder.h>
#include <LibGUI/BoxLayout.h>
#include <LibGUI/JSSyntaxHighlighter.h>
#include <LibGUI/TextBox.h>
#include <LibWeb/DOM/DocumentType.h>
#include <LibWeb/DOM/ElementFactory.h>
#include <LibWeb/DOM/Text.h>
#include <LibWeb/DOMTreeModel.h>
#include <LibWeb/HTML/HTMLBodyElement.h>
namespace Browser {
JS::Value BrowserConsoleClient::log()
{
m_console_widget.print_html(vm().join_arguments());
return JS::js_undefined();
}
JS::Value BrowserConsoleClient::info()
{
StringBuilder html;
html.append("<span class=\"info\">");
html.append("(i) ");
html.append(vm().join_arguments());
html.append("</span>");
m_console_widget.print_html(html.string_view());
return JS::js_undefined();
}
JS::Value BrowserConsoleClient::debug()
{
StringBuilder html;
html.append("<span class=\"debug\">");
html.append("(d) ");
html.append(vm().join_arguments());
html.append("</span>");
m_console_widget.print_html(html.string_view());
return JS::js_undefined();
}
JS::Value BrowserConsoleClient::warn()
{
StringBuilder html;
html.append("<span class=\"warn\">");
html.append("(w) ");
html.append(vm().join_arguments());
html.append("</span>");
m_console_widget.print_html(html.string_view());
return JS::js_undefined();
}
JS::Value BrowserConsoleClient::error()
{
StringBuilder html;
html.append("<span class=\"error\">");
html.append("(e) ");
html.append(vm().join_arguments());
html.append("</span>");
m_console_widget.print_html(html.string_view());
return JS::js_undefined();
}
JS::Value BrowserConsoleClient::clear()
{
m_console_widget.clear_output();
return JS::js_undefined();
}
JS::Value BrowserConsoleClient::trace()
{
StringBuilder html;
html.append(vm().join_arguments());
auto trace = get_trace();
for (auto& function_name : trace) {
if (function_name.is_empty())
function_name = "&lt;anonymous&gt;";
html.appendff(" -> {}<br>", function_name);
}
m_console_widget.print_html(html.string_view());
return JS::js_undefined();
}
JS::Value BrowserConsoleClient::count()
{
auto label = vm().argument_count() ? vm().argument(0).to_string_without_side_effects() : "default";
auto counter_value = m_console.counter_increment(label);
m_console_widget.print_html(String::formatted("{}: {}", label, counter_value));
return JS::js_undefined();
}
JS::Value BrowserConsoleClient::count_reset()
{
auto label = vm().argument_count() ? vm().argument(0).to_string_without_side_effects() : "default";
if (m_console.counter_reset(label)) {
m_console_widget.print_html(String::formatted("{}: 0", label));
} else {
m_console_widget.print_html(String::formatted("\"{}\" doesn't have a count", label));
}
return JS::js_undefined();
}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2020, Hunter Salyer <thefalsehonesty@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <LibGUI/Widget.h>
#include <LibJS/Console.h>
#include <LibJS/Forward.h>
#include <LibWeb/InProcessWebView.h>
namespace Browser {
class ConsoleWidget;
class BrowserConsoleClient final : public JS::ConsoleClient {
public:
BrowserConsoleClient(JS::Console& console, ConsoleWidget& console_widget)
: ConsoleClient(console)
, m_console_widget(console_widget)
{
}
private:
virtual JS::Value log() override;
virtual JS::Value info() override;
virtual JS::Value debug() override;
virtual JS::Value warn() override;
virtual JS::Value error() override;
virtual JS::Value clear() override;
virtual JS::Value trace() override;
virtual JS::Value count() override;
virtual JS::Value count_reset() override;
ConsoleWidget& m_console_widget;
};
}

View file

@ -0,0 +1,15 @@
@GUI::Widget {
name: "browser"
fill_with_background_color: true
layout: @GUI::VerticalBoxLayout {
spacing: 2
}
@GUI::TabWidget {
name: "tab_widget"
container_padding: 0
uniform_tabs: true
text_alignment: "CenterLeft"
}
}

View file

@ -0,0 +1,19 @@
compile_gml(BrowserWindow.gml BrowserWindowGML.h browser_window_gml)
compile_gml(Tab.gml TabGML.h tab_gml)
set(SOURCES
BookmarksBarWidget.cpp
BrowserConsoleClient.cpp
ConsoleWidget.cpp
DownloadWidget.cpp
History.cpp
InspectorWidget.cpp
main.cpp
Tab.cpp
WindowActions.cpp
BrowserWindowGML.h
TabGML.h
)
serenity_app(Browser ICON app-browser)
target_link_libraries(Browser LibWeb LibProtocol LibGUI LibDesktop)

View file

@ -0,0 +1,169 @@
/*
* Copyright (c) 2020, Hunter Salyer <thefalsehonesty@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "ConsoleWidget.h"
#include <AK/StringBuilder.h>
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Button.h>
#include <LibGUI/JSSyntaxHighlighter.h>
#include <LibGUI/TextBox.h>
#include <LibGfx/FontDatabase.h>
#include <LibJS/Interpreter.h>
#include <LibJS/MarkupGenerator.h>
#include <LibJS/Parser.h>
#include <LibJS/Runtime/Error.h>
#include <LibWeb/DOM/DocumentType.h>
#include <LibWeb/DOM/ElementFactory.h>
#include <LibWeb/DOM/Text.h>
#include <LibWeb/DOMTreeModel.h>
#include <LibWeb/HTML/HTMLBodyElement.h>
namespace Browser {
ConsoleWidget::ConsoleWidget()
{
set_layout<GUI::VerticalBoxLayout>();
set_fill_with_background_color(true);
auto base_document = Web::DOM::Document::create();
base_document->append_child(adopt(*new Web::DOM::DocumentType(base_document)));
auto html_element = base_document->create_element("html");
base_document->append_child(html_element);
auto head_element = base_document->create_element("head");
html_element->append_child(head_element);
auto body_element = base_document->create_element("body");
html_element->append_child(body_element);
m_output_container = body_element;
m_output_view = add<Web::InProcessWebView>();
m_output_view->set_document(base_document);
auto& bottom_container = add<GUI::Widget>();
bottom_container.set_layout<GUI::HorizontalBoxLayout>();
bottom_container.set_fixed_height(22);
m_input = bottom_container.add<GUI::TextBox>();
m_input->set_syntax_highlighter(make<GUI::JSSyntaxHighlighter>());
// FIXME: Syntax Highlighting breaks the cursor's position on non fixed-width fonts.
m_input->set_font(Gfx::FontDatabase::default_fixed_width_font());
m_input->set_history_enabled(true);
m_input->on_return_pressed = [this] {
auto js_source = m_input->text();
// FIXME: An is_blank check to check if there is only whitespace would probably be preferable.
if (js_source.is_empty())
return;
m_input->add_current_text_to_history();
m_input->clear();
print_source_line(js_source);
auto parser = JS::Parser(JS::Lexer(js_source));
auto program = parser.parse_program();
StringBuilder output_html;
if (parser.has_errors()) {
auto error = parser.errors()[0];
auto hint = error.source_location_hint(js_source);
if (!hint.is_empty())
output_html.append(String::formatted("<pre>{}</pre>", escape_html_entities(hint)));
m_interpreter->vm().throw_exception<JS::SyntaxError>(m_interpreter->global_object(), error.to_string());
} else {
m_interpreter->run(m_interpreter->global_object(), *program);
}
if (m_interpreter->exception()) {
output_html.append("Uncaught exception: ");
output_html.append(JS::MarkupGenerator::html_from_value(m_interpreter->exception()->value()));
print_html(output_html.string_view());
m_interpreter->vm().clear_exception();
return;
}
print_html(JS::MarkupGenerator::html_from_value(m_interpreter->vm().last_value()));
};
set_focus_proxy(m_input);
auto& clear_button = bottom_container.add<GUI::Button>();
clear_button.set_fixed_size(22, 22);
clear_button.set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/delete.png"));
clear_button.set_tooltip("Clear the console output");
clear_button.on_click = [this](auto) {
clear_output();
};
}
ConsoleWidget::~ConsoleWidget()
{
}
void ConsoleWidget::set_interpreter(WeakPtr<JS::Interpreter> interpreter)
{
if (m_interpreter.ptr() == interpreter.ptr())
return;
m_interpreter = interpreter;
m_console_client = make<BrowserConsoleClient>(interpreter->global_object().console(), *this);
interpreter->global_object().console().set_client(*m_console_client.ptr());
clear_output();
}
void ConsoleWidget::print_source_line(const StringView& source)
{
StringBuilder html;
html.append("<span class=\"repl-indicator\">");
html.append("&gt; ");
html.append("</span>");
html.append(JS::MarkupGenerator::html_from_source(source));
print_html(html.string_view());
}
void ConsoleWidget::print_html(const StringView& line)
{
auto paragraph = m_output_container->document().create_element("p");
paragraph->set_inner_html(line);
m_output_container->append_child(paragraph);
m_output_container->document().invalidate_layout();
m_output_container->document().update_layout();
m_output_view->scroll_to_bottom();
}
void ConsoleWidget::clear_output()
{
m_output_container->remove_all_children();
m_output_view->update();
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2020, Hunter Salyer <thefalsehonesty@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "BrowserConsoleClient.h"
#include "History.h"
#include <LibGUI/Widget.h>
#include <LibJS/Forward.h>
#include <LibWeb/InProcessWebView.h>
namespace Browser {
class ConsoleWidget final : public GUI::Widget {
C_OBJECT(ConsoleWidget)
public:
virtual ~ConsoleWidget();
void set_interpreter(WeakPtr<JS::Interpreter>);
void print_source_line(const StringView&);
void print_html(const StringView&);
void clear_output();
private:
ConsoleWidget();
RefPtr<GUI::TextBox> m_input;
RefPtr<Web::InProcessWebView> m_output_view;
RefPtr<Web::DOM::Element> m_output_container;
WeakPtr<JS::Interpreter> m_interpreter;
OwnPtr<BrowserConsoleClient> m_console_client;
};
}

View file

@ -0,0 +1,182 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "DownloadWidget.h"
#include <AK/NumberFormat.h>
#include <AK/SharedBuffer.h>
#include <AK/StringBuilder.h>
#include <LibCore/File.h>
#include <LibCore/FileStream.h>
#include <LibCore/StandardPaths.h>
#include <LibDesktop/Launcher.h>
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Button.h>
#include <LibGUI/ImageWidget.h>
#include <LibGUI/Label.h>
#include <LibGUI/MessageBox.h>
#include <LibGUI/ProgressBar.h>
#include <LibGUI/Window.h>
#include <LibProtocol/Client.h>
#include <LibWeb/Loader/ResourceLoader.h>
#include <math.h>
namespace Browser {
DownloadWidget::DownloadWidget(const URL& url)
: m_url(url)
{
{
StringBuilder builder;
builder.append(Core::StandardPaths::downloads_directory());
builder.append('/');
builder.append(m_url.basename());
m_destination_path = builder.to_string();
}
m_elapsed_timer.start();
m_download = Web::ResourceLoader::the().protocol_client().start_download("GET", url.to_string());
ASSERT(m_download);
m_download->on_progress = [this](Optional<u32> total_size, u32 downloaded_size) {
did_progress(total_size.value(), downloaded_size);
};
{
auto file_or_error = Core::File::open(m_destination_path, Core::IODevice::WriteOnly);
if (file_or_error.is_error()) {
GUI::MessageBox::show(window(), String::formatted("Cannot open {} for writing", m_destination_path), "Download failed", GUI::MessageBox::Type::Error);
window()->close();
return;
}
m_output_file_stream = make<Core::OutputFileStream>(*file_or_error.value());
}
m_download->on_finish = [this](bool success, auto) { did_finish(success); };
m_download->stream_into(*m_output_file_stream);
set_fill_with_background_color(true);
auto& layout = set_layout<GUI::VerticalBoxLayout>();
layout.set_margins({ 4, 4, 4, 4 });
auto& animation_container = add<GUI::Widget>();
animation_container.set_fixed_height(32);
auto& animation_layout = animation_container.set_layout<GUI::HorizontalBoxLayout>();
auto& browser_image = animation_container.add<GUI::ImageWidget>();
browser_image.load_from_file("/res/graphics/download-animation.gif");
animation_layout.add_spacer();
auto& source_label = add<GUI::Label>(String::formatted("From: {}", url));
source_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
source_label.set_fixed_height(16);
m_progress_bar = add<GUI::ProgressBar>();
m_progress_bar->set_fixed_height(20);
m_progress_label = add<GUI::Label>();
m_progress_label->set_text_alignment(Gfx::TextAlignment::CenterLeft);
m_progress_label->set_fixed_height(16);
auto& destination_label = add<GUI::Label>(String::formatted("To: {}", m_destination_path));
destination_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
destination_label.set_fixed_height(16);
auto& button_container = add<GUI::Widget>();
auto& button_container_layout = button_container.set_layout<GUI::HorizontalBoxLayout>();
button_container_layout.add_spacer();
m_cancel_button = button_container.add<GUI::Button>("Cancel");
m_cancel_button->set_fixed_size(100, 22);
m_cancel_button->on_click = [this](auto) {
bool success = m_download->stop();
ASSERT(success);
window()->close();
};
m_close_button = button_container.add<GUI::Button>("OK");
m_close_button->set_enabled(false);
m_close_button->set_fixed_size(100, 22);
m_close_button->on_click = [this](auto) {
window()->close();
};
}
DownloadWidget::~DownloadWidget()
{
}
void DownloadWidget::did_progress(Optional<u32> total_size, u32 downloaded_size)
{
m_progress_bar->set_min(0);
if (total_size.has_value()) {
int percent = roundf(((float)downloaded_size / (float)total_size.value()) * 100.0f);
window()->set_progress(percent);
m_progress_bar->set_max(total_size.value());
} else {
m_progress_bar->set_max(0);
}
m_progress_bar->set_value(downloaded_size);
{
StringBuilder builder;
builder.append("Downloaded ");
builder.append(human_readable_size(downloaded_size));
builder.appendff(" in {} sec", m_elapsed_timer.elapsed() / 1000);
m_progress_label->set_text(builder.to_string());
}
{
StringBuilder builder;
if (total_size.has_value()) {
int percent = roundf(((float)downloaded_size / (float)total_size.value()) * 100);
builder.appendff("{}%", percent);
} else {
builder.append(human_readable_size(downloaded_size));
}
builder.append(" of ");
builder.append(m_url.basename());
window()->set_title(builder.to_string());
}
}
void DownloadWidget::did_finish(bool success)
{
dbgln("did_finish, success={}", success);
m_close_button->set_enabled(true);
m_cancel_button->set_text("Open in Folder");
m_cancel_button->on_click = [this](auto) {
Desktop::Launcher::open(URL::create_with_file_protocol(Core::StandardPaths::downloads_directory()));
window()->close();
};
m_cancel_button->update();
if (!success) {
GUI::MessageBox::show(window(), String::formatted("Download failed for some reason"), "Download failed", GUI::MessageBox::Type::Error);
window()->close();
return;
}
}
}

View file

@ -0,0 +1,61 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/URL.h>
#include <LibCore/ElapsedTimer.h>
#include <LibCore/FileStream.h>
#include <LibGUI/ProgressBar.h>
#include <LibGUI/Widget.h>
#include <LibProtocol/Download.h>
namespace Browser {
class DownloadWidget final : public GUI::Widget {
C_OBJECT(DownloadWidget);
public:
virtual ~DownloadWidget() override;
private:
explicit DownloadWidget(const URL&);
void did_progress(Optional<u32> total_size, u32 downloaded_size);
void did_finish(bool success);
URL m_url;
String m_destination_path;
RefPtr<Protocol::Download> m_download;
RefPtr<GUI::ProgressBar> m_progress_bar;
RefPtr<GUI::Label> m_progress_label;
RefPtr<GUI::Button> m_cancel_button;
RefPtr<GUI::Button> m_close_button;
OwnPtr<Core::OutputFileStream> m_output_file_stream;
Core::ElapsedTimer m_elapsed_timer;
};
}

View file

@ -0,0 +1,73 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "History.h"
namespace Browser {
void History::dump() const
{
dbgln("Dump {} items(s)", m_items.size());
int i = 0;
for (auto& item : m_items) {
dbgln("[{}] {} {}", i, item, m_current == i ? '*' : ' ');
++i;
}
}
void History::push(const URL& url)
{
m_items.shrink(m_current + 1);
m_items.append(url);
m_current++;
}
URL History::current() const
{
if (m_current == -1)
return {};
return m_items[m_current];
}
void History::go_back()
{
ASSERT(can_go_back());
m_current--;
}
void History::go_forward()
{
ASSERT(can_go_forward());
m_current++;
}
void History::clear()
{
m_items = {};
m_current = -1;
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/URL.h>
#include <AK/Vector.h>
namespace Browser {
class History {
public:
void dump() const;
void push(const URL&);
URL current() const;
void go_back();
void go_forward();
bool can_go_back() { return m_current > 0; }
bool can_go_forward() { return m_current + 1 < static_cast<int>(m_items.size()); }
void clear();
private:
Vector<URL> m_items;
int m_current { -1 };
};
}

View file

@ -0,0 +1,94 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "InspectorWidget.h"
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Splitter.h>
#include <LibGUI/TabWidget.h>
#include <LibGUI/TableView.h>
#include <LibGUI/TreeView.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Element.h>
#include <LibWeb/DOMTreeModel.h>
#include <LibWeb/LayoutTreeModel.h>
#include <LibWeb/StylePropertiesModel.h>
namespace Browser {
void InspectorWidget::set_inspected_node(Web::DOM::Node* node)
{
m_document->set_inspected_node(node);
if (node && node->is_element()) {
auto& element = downcast<Web::DOM::Element>(*node);
if (element.specified_css_values()) {
m_style_table_view->set_model(Web::StylePropertiesModel::create(*element.specified_css_values()));
m_computed_style_table_view->set_model(Web::StylePropertiesModel::create(*element.computed_style()));
}
} else {
m_style_table_view->set_model(nullptr);
m_computed_style_table_view->set_model(nullptr);
}
}
InspectorWidget::InspectorWidget()
{
set_layout<GUI::VerticalBoxLayout>();
auto& splitter = add<GUI::VerticalSplitter>();
auto& top_tab_widget = splitter.add<GUI::TabWidget>();
m_dom_tree_view = top_tab_widget.add_tab<GUI::TreeView>("DOM");
m_dom_tree_view->on_selection = [this](auto& index) {
auto* node = static_cast<Web::DOM::Node*>(index.internal_data());
set_inspected_node(node);
};
m_layout_tree_view = top_tab_widget.add_tab<GUI::TreeView>("Layout");
m_layout_tree_view->on_selection = [this](auto& index) {
auto* node = static_cast<Web::Layout::Node*>(index.internal_data());
set_inspected_node(node->dom_node());
};
auto& bottom_tab_widget = splitter.add<GUI::TabWidget>();
m_style_table_view = bottom_tab_widget.add_tab<GUI::TableView>("Styles");
m_computed_style_table_view = bottom_tab_widget.add_tab<GUI::TableView>("Computed");
}
InspectorWidget::~InspectorWidget()
{
}
void InspectorWidget::set_document(Web::DOM::Document* document)
{
if (m_document == document)
return;
m_document = document;
m_dom_tree_view->set_model(Web::DOMTreeModel::create(*document));
m_layout_tree_view->set_model(Web::LayoutTreeModel::create(*document));
}
}

View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <LibGUI/Widget.h>
#include <LibWeb/Forward.h>
namespace Browser {
class InspectorWidget final : public GUI::Widget {
C_OBJECT(InspectorWidget)
public:
virtual ~InspectorWidget();
void set_document(Web::DOM::Document*);
private:
InspectorWidget();
void set_inspected_node(Web::DOM::Node*);
RefPtr<GUI::TreeView> m_dom_tree_view;
RefPtr<GUI::TreeView> m_layout_tree_view;
RefPtr<GUI::TableView> m_style_table_view;
RefPtr<GUI::TableView> m_computed_style_table_view;
RefPtr<Web::DOM::Document> m_document;
};
}

View file

@ -0,0 +1,550 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "Tab.h"
#include "BookmarksBarWidget.h"
#include "Browser.h"
#include "ConsoleWidget.h"
#include "DownloadWidget.h"
#include "InspectorWidget.h"
#include "WindowActions.h"
#include <AK/StringBuilder.h>
#include <Applications/Browser/TabGML.h>
#include <LibGUI/Action.h>
#include <LibGUI/Application.h>
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Button.h>
#include <LibGUI/Clipboard.h>
#include <LibGUI/Menu.h>
#include <LibGUI/MenuBar.h>
#include <LibGUI/StatusBar.h>
#include <LibGUI/TabWidget.h>
#include <LibGUI/TextBox.h>
#include <LibGUI/ToolBar.h>
#include <LibGUI/ToolBarContainer.h>
#include <LibGUI/Window.h>
#include <LibJS/Interpreter.h>
#include <LibWeb/CSS/Parser/CSSParser.h>
#include <LibWeb/DOM/Element.h>
#include <LibWeb/DOMTreeModel.h>
#include <LibWeb/Dump.h>
#include <LibWeb/InProcessWebView.h>
#include <LibWeb/Layout/BlockBox.h>
#include <LibWeb/Layout/InitialContainingBlockBox.h>
#include <LibWeb/Layout/InlineNode.h>
#include <LibWeb/Layout/Node.h>
#include <LibWeb/Loader/ResourceLoader.h>
#include <LibWeb/OutOfProcessWebView.h>
#include <LibWeb/Page/Frame.h>
namespace Browser {
URL url_from_user_input(const String& input)
{
auto url = URL(input);
if (url.is_valid())
return url;
StringBuilder builder;
builder.append("http://");
builder.append(input);
return URL(builder.build());
}
static void start_download(const URL& url)
{
auto window = GUI::Window::construct();
window->resize(300, 150);
window->set_title(String::formatted("0% of {}", url.basename()));
window->set_resizable(false);
window->set_main_widget<DownloadWidget>(url);
window->show();
[[maybe_unused]] auto& unused = window.leak_ref();
}
Tab::Tab(Type type)
: m_type(type)
{
load_from_gml(tab_gml);
m_toolbar_container = *find_descendant_of_type_named<GUI::ToolBarContainer>("toolbar_container");
auto& toolbar = *find_descendant_of_type_named<GUI::ToolBar>("toolbar");
auto& webview_container = *find_descendant_of_type_named<GUI::Widget>("webview_container");
if (m_type == Type::InProcessWebView)
m_page_view = webview_container.add<Web::InProcessWebView>();
else
m_web_content_view = webview_container.add<Web::OutOfProcessWebView>();
m_go_back_action = GUI::CommonActions::make_go_back_action([this](auto&) { go_back(); }, this);
m_go_forward_action = GUI::CommonActions::make_go_forward_action([this](auto&) { go_forward(); }, this);
toolbar.add_action(*m_go_back_action);
toolbar.add_action(*m_go_forward_action);
toolbar.add_action(GUI::CommonActions::make_go_home_action([this](auto&) { load(g_home_url); }, this));
m_reload_action = GUI::CommonActions::make_reload_action([this](auto&) { reload(); }, this);
toolbar.add_action(*m_reload_action);
m_location_box = toolbar.add<GUI::TextBox>();
m_location_box->set_placeholder("Address");
m_location_box->on_return_pressed = [this] {
auto url = url_from_user_input(m_location_box->text());
load(url);
view().set_focus(true);
};
m_location_box->add_custom_context_menu_action(GUI::Action::create("Paste & Go", [this](auto&) {
m_location_box->set_text(GUI::Clipboard::the().data());
m_location_box->on_return_pressed();
}));
m_bookmark_button = toolbar.add<GUI::Button>();
m_bookmark_button->set_button_style(Gfx::ButtonStyle::CoolBar);
m_bookmark_button->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/bookmark-contour.png"));
m_bookmark_button->set_fixed_size(22, 22);
m_bookmark_button->on_click = [this](auto) {
auto url = this->url().to_string();
if (BookmarksBarWidget::the().contains_bookmark(url)) {
BookmarksBarWidget::the().remove_bookmark(url);
} else {
BookmarksBarWidget::the().add_bookmark(url, m_title);
}
update_bookmark_button(url);
};
hooks().on_load_start = [this](auto& url) {
m_location_box->set_icon(nullptr);
m_location_box->set_text(url.to_string());
// don't add to history if back or forward is pressed
if (!m_is_history_navigation)
m_history.push(url);
m_is_history_navigation = false;
update_actions();
update_bookmark_button(url.to_string());
};
hooks().on_link_click = [this](auto& url, auto& target, unsigned modifiers) {
if (target == "_blank" || modifiers == Mod_Ctrl) {
on_tab_open_request(url);
} else {
load(url);
}
};
m_link_context_menu = GUI::Menu::construct();
auto link_default_action = GUI::Action::create("Open", [this](auto&) {
hooks().on_link_click(m_link_context_menu_url, "", 0);
});
m_link_context_menu->add_action(link_default_action);
m_link_context_menu_default_action = link_default_action;
m_link_context_menu->add_action(GUI::Action::create("Open in new tab", [this](auto&) {
hooks().on_link_click(m_link_context_menu_url, "_blank", 0);
}));
m_link_context_menu->add_separator();
m_link_context_menu->add_action(GUI::Action::create("Copy link", [this](auto&) {
GUI::Clipboard::the().set_plain_text(m_link_context_menu_url.to_string());
}));
m_link_context_menu->add_separator();
m_link_context_menu->add_action(GUI::Action::create("Download", [this](auto&) {
start_download(m_link_context_menu_url);
}));
hooks().on_link_context_menu_request = [this](auto& url, auto& screen_position) {
m_link_context_menu_url = url;
m_link_context_menu->popup(screen_position, m_link_context_menu_default_action);
};
m_image_context_menu = GUI::Menu::construct();
m_image_context_menu->add_action(GUI::Action::create("Open image", [this](auto&) {
hooks().on_link_click(m_image_context_menu_url, "", 0);
}));
m_image_context_menu->add_action(GUI::Action::create("Open image in new tab", [this](auto&) {
hooks().on_link_click(m_image_context_menu_url, "_blank", 0);
}));
m_image_context_menu->add_separator();
m_image_context_menu->add_action(GUI::Action::create("Copy image", [this](auto&) {
if (m_image_context_menu_bitmap.is_valid())
GUI::Clipboard::the().set_bitmap(*m_image_context_menu_bitmap.bitmap());
}));
m_image_context_menu->add_action(GUI::Action::create("Copy image URL", [this](auto&) {
GUI::Clipboard::the().set_plain_text(m_image_context_menu_url.to_string());
}));
m_image_context_menu->add_separator();
m_image_context_menu->add_action(GUI::Action::create("Download", [this](auto&) {
start_download(m_image_context_menu_url);
}));
hooks().on_image_context_menu_request = [this](auto& image_url, auto& screen_position, const Gfx::ShareableBitmap& shareable_bitmap) {
m_image_context_menu_url = image_url;
m_image_context_menu_bitmap = shareable_bitmap;
m_image_context_menu->popup(screen_position);
};
hooks().on_link_middle_click = [this](auto& href, auto&, auto) {
hooks().on_link_click(href, "_blank", 0);
};
hooks().on_title_change = [this](auto& title) {
if (title.is_null()) {
m_title = url().to_string();
} else {
m_title = title;
}
if (on_title_change)
on_title_change(m_title);
};
hooks().on_favicon_change = [this](auto& icon) {
m_icon = icon;
m_location_box->set_icon(&icon);
if (on_favicon_change)
on_favicon_change(icon);
};
// FIXME: Support JS console in multi-process mode.
if (m_type == Type::InProcessWebView) {
hooks().on_set_document = [this](auto* document) {
if (document && m_console_window) {
auto* console_widget = static_cast<ConsoleWidget*>(m_console_window->main_widget());
console_widget->set_interpreter(document->interpreter().make_weak_ptr());
}
};
}
auto focus_location_box_action = GUI::Action::create(
"Focus location box", { Mod_Ctrl, Key_L }, [this](auto&) {
m_location_box->select_all();
m_location_box->set_focus(true);
},
this);
m_statusbar = *find_descendant_of_type_named<GUI::StatusBar>("statusbar");
hooks().on_link_hover = [this](auto& url) {
if (url.is_valid())
m_statusbar->set_text(url.to_string());
else
m_statusbar->set_text("");
};
hooks().on_url_drop = [this](auto& url) {
load(url);
};
m_menubar = GUI::MenuBar::construct();
auto& app_menu = m_menubar->add_menu("Browser");
app_menu.add_action(WindowActions::the().create_new_tab_action());
app_menu.add_action(GUI::Action::create(
"Close tab", { Mod_Ctrl, Key_W }, Gfx::Bitmap::load_from_file("/res/icons/16x16/close-tab.png"), [this](auto&) {
on_tab_close_request(*this);
},
this));
app_menu.add_action(*m_reload_action);
app_menu.add_separator();
app_menu.add_action(GUI::CommonActions::make_quit_action([](auto&) {
GUI::Application::the()->quit();
}));
auto& view_menu = m_menubar->add_menu("View");
view_menu.add_action(GUI::CommonActions::make_fullscreen_action(
[this](auto&) {
window()->set_fullscreen(!window()->is_fullscreen());
auto is_fullscreen = window()->is_fullscreen();
auto* tab_widget = static_cast<GUI::TabWidget*>(parent_widget());
tab_widget->set_bar_visible(!is_fullscreen && tab_widget->children().size() > 1);
m_toolbar_container->set_visible(!is_fullscreen);
m_statusbar->set_visible(!is_fullscreen);
},
this));
auto view_source_action = GUI::Action::create(
"View source", { Mod_Ctrl, Key_U }, [this](auto&) {
if (m_type == Type::InProcessWebView) {
ASSERT(m_page_view->document());
auto url = m_page_view->document()->url().to_string();
auto source = m_page_view->document()->source();
auto window = GUI::Window::construct();
auto& editor = window->set_main_widget<GUI::TextEditor>();
editor.set_text(source);
editor.set_mode(GUI::TextEditor::ReadOnly);
editor.set_ruler_visible(true);
window->resize(640, 480);
window->set_title(url);
window->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-text.png"));
window->show();
[[maybe_unused]] auto& unused = window.leak_ref();
} else {
TODO();
}
},
this);
auto inspect_dom_tree_action = GUI::Action::create(
"Inspect DOM tree", { Mod_None, Key_F12 }, [this](auto&) {
if (m_type == Type::InProcessWebView) {
if (!m_dom_inspector_window) {
m_dom_inspector_window = GUI::Window::construct();
m_dom_inspector_window->resize(300, 500);
m_dom_inspector_window->set_title("DOM inspector");
m_dom_inspector_window->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/inspector-object.png"));
m_dom_inspector_window->set_main_widget<InspectorWidget>();
}
auto* inspector_widget = static_cast<InspectorWidget*>(m_dom_inspector_window->main_widget());
inspector_widget->set_document(m_page_view->document());
m_dom_inspector_window->show();
m_dom_inspector_window->move_to_front();
} else {
TODO();
}
},
this);
auto& inspect_menu = m_menubar->add_menu("Inspect");
inspect_menu.add_action(*view_source_action);
inspect_menu.add_action(*inspect_dom_tree_action);
inspect_menu.add_action(GUI::Action::create(
"Open JS Console", { Mod_Ctrl, Key_I }, [this](auto&) {
if (m_type == Type::InProcessWebView) {
if (!m_console_window) {
m_console_window = GUI::Window::construct();
m_console_window->resize(500, 300);
m_console_window->set_title("JS Console");
m_console_window->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-javascript.png"));
m_console_window->set_main_widget<ConsoleWidget>();
}
auto* console_widget = static_cast<ConsoleWidget*>(m_console_window->main_widget());
console_widget->set_interpreter(m_page_view->document()->interpreter().make_weak_ptr());
m_console_window->show();
m_console_window->move_to_front();
} else {
TODO();
}
},
this));
auto& debug_menu = m_menubar->add_menu("Debug");
debug_menu.add_action(GUI::Action::create(
"Dump DOM tree", [this](auto&) {
if (m_type == Type::InProcessWebView) {
Web::dump_tree(*m_page_view->document());
} else {
TODO();
}
},
this));
debug_menu.add_action(GUI::Action::create(
"Dump Layout tree", [this](auto&) {
if (m_type == Type::InProcessWebView) {
Web::dump_tree(*m_page_view->document()->layout_node());
} else {
TODO();
}
},
this));
debug_menu.add_action(GUI::Action::create(
"Dump Style sheets", [this](auto&) {
if (m_type == Type::InProcessWebView) {
for (auto& sheet : m_page_view->document()->style_sheets().sheets()) {
Web::dump_sheet(sheet);
}
} else {
TODO();
}
},
this));
debug_menu.add_action(GUI::Action::create("Dump history", { Mod_Ctrl, Key_H }, [&](auto&) {
m_history.dump();
}));
debug_menu.add_separator();
auto line_box_borders_action = GUI::Action::create_checkable(
"Line box borders", [this](auto& action) {
if (m_type == Type::InProcessWebView) {
m_page_view->set_should_show_line_box_borders(action.is_checked());
m_page_view->update();
} else {
TODO();
}
},
this);
line_box_borders_action->set_checked(false);
debug_menu.add_action(line_box_borders_action);
debug_menu.add_separator();
debug_menu.add_action(GUI::Action::create("Collect garbage", { Mod_Ctrl | Mod_Shift, Key_G }, [this](auto&) {
if (m_type == Type::InProcessWebView) {
if (auto* document = m_page_view->document()) {
document->interpreter().heap().collect_garbage(JS::Heap::CollectionType::CollectGarbage, true);
}
} else {
TODO();
}
}));
auto& bookmarks_menu = m_menubar->add_menu("Bookmarks");
bookmarks_menu.add_action(WindowActions::the().show_bookmarks_bar_action());
auto& help_menu = m_menubar->add_menu("Help");
help_menu.add_action(WindowActions::the().about_action());
m_tab_context_menu = GUI::Menu::construct();
m_tab_context_menu->add_action(GUI::Action::create("Reload Tab", [this](auto&) {
m_reload_action->activate();
}));
m_tab_context_menu->add_action(GUI::Action::create("Close Tab", [this](auto&) {
on_tab_close_request(*this);
}));
m_page_context_menu = GUI::Menu::construct();
m_page_context_menu->add_action(*m_go_back_action);
m_page_context_menu->add_action(*m_go_forward_action);
m_page_context_menu->add_action(*m_reload_action);
m_page_context_menu->add_separator();
m_page_context_menu->add_action(*view_source_action);
m_page_context_menu->add_action(*inspect_dom_tree_action);
hooks().on_context_menu_request = [&](auto& screen_position) {
m_page_context_menu->popup(screen_position);
};
}
Tab::~Tab()
{
}
void Tab::load(const URL& url, LoadType load_type)
{
m_is_history_navigation = (load_type == LoadType::HistoryNavigation);
if (m_type == Type::InProcessWebView)
m_page_view->load(url);
else
m_web_content_view->load(url);
}
URL Tab::url() const
{
if (m_type == Type::InProcessWebView)
return m_page_view->url();
return m_web_content_view->url();
}
void Tab::reload()
{
load(url());
}
void Tab::go_back()
{
m_history.go_back();
update_actions();
load(m_history.current(), LoadType::HistoryNavigation);
}
void Tab::go_forward()
{
m_history.go_forward();
update_actions();
load(m_history.current(), LoadType::HistoryNavigation);
}
void Tab::update_actions()
{
m_go_back_action->set_enabled(m_history.can_go_back());
m_go_forward_action->set_enabled(m_history.can_go_forward());
}
void Tab::update_bookmark_button(const String& url)
{
if (BookmarksBarWidget::the().contains_bookmark(url)) {
m_bookmark_button->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/bookmark-filled.png"));
m_bookmark_button->set_tooltip("Remove Bookmark");
} else {
m_bookmark_button->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/bookmark-contour.png"));
m_bookmark_button->set_tooltip("Add Bookmark");
}
}
void Tab::did_become_active()
{
Web::ResourceLoader::the().on_load_counter_change = [this] {
if (Web::ResourceLoader::the().pending_loads() == 0) {
m_statusbar->set_text("");
return;
}
m_statusbar->set_text(String::formatted("Loading ({} pending resources...)", Web::ResourceLoader::the().pending_loads()));
};
BookmarksBarWidget::the().on_bookmark_click = [this](auto& url, unsigned modifiers) {
if (modifiers & Mod_Ctrl)
on_tab_open_request(url);
else
load(url);
};
BookmarksBarWidget::the().on_bookmark_hover = [this](auto&, auto& url) {
m_statusbar->set_text(url);
};
BookmarksBarWidget::the().remove_from_parent();
m_toolbar_container->add_child(BookmarksBarWidget::the());
auto is_fullscreen = window()->is_fullscreen();
m_toolbar_container->set_visible(!is_fullscreen);
m_statusbar->set_visible(!is_fullscreen);
GUI::Application::the()->set_menubar(m_menubar);
}
void Tab::context_menu_requested(const Gfx::IntPoint& screen_position)
{
m_tab_context_menu->popup(screen_position);
}
GUI::Widget& Tab::view()
{
if (m_type == Type::InProcessWebView)
return *m_page_view;
return *m_web_content_view;
}
Web::WebViewHooks& Tab::hooks()
{
if (m_type == Type::InProcessWebView)
return *m_page_view;
return *m_web_content_view;
}
}

View file

@ -0,0 +1,22 @@
@GUI::Widget {
layout: @GUI::VerticalBoxLayout {
}
@GUI::ToolBarContainer {
name: "toolbar_container"
@GUI::ToolBar {
name: "toolbar"
}
}
@GUI::Widget {
name: "webview_container"
layout: @GUI::VerticalBoxLayout {
}
}
@GUI::StatusBar {
name: "statusbar"
}
}

View file

@ -0,0 +1,123 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "History.h"
#include <AK/URL.h>
#include <LibGUI/Widget.h>
#include <LibGfx/ShareableBitmap.h>
#include <LibHTTP/HttpJob.h>
#include <LibWeb/Forward.h>
namespace Web {
class OutOfProcessWebView;
class WebViewHooks;
}
namespace Browser {
class Tab final : public GUI::Widget {
C_OBJECT(Tab);
public:
enum class Type {
InProcessWebView,
OutOfProcessWebView,
};
virtual ~Tab() override;
URL url() const;
enum class LoadType {
Normal,
HistoryNavigation,
};
void load(const URL&, LoadType = LoadType::Normal);
void reload();
void go_back();
void go_forward();
void did_become_active();
void context_menu_requested(const Gfx::IntPoint& screen_position);
Function<void(String)> on_title_change;
Function<void(const URL&)> on_tab_open_request;
Function<void(Tab&)> on_tab_close_request;
Function<void(const Gfx::Bitmap&)> on_favicon_change;
const String& title() const { return m_title; }
const Gfx::Bitmap* icon() const { return m_icon; }
GUI::Widget& view();
private:
explicit Tab(Type);
Web::WebViewHooks& hooks();
void update_actions();
void update_bookmark_button(const String& url);
Type m_type;
History m_history;
RefPtr<Web::InProcessWebView> m_page_view;
RefPtr<Web::OutOfProcessWebView> m_web_content_view;
RefPtr<GUI::Action> m_go_back_action;
RefPtr<GUI::Action> m_go_forward_action;
RefPtr<GUI::Action> m_reload_action;
RefPtr<GUI::TextBox> m_location_box;
RefPtr<GUI::Button> m_bookmark_button;
RefPtr<GUI::Window> m_dom_inspector_window;
RefPtr<GUI::Window> m_console_window;
RefPtr<GUI::StatusBar> m_statusbar;
RefPtr<GUI::MenuBar> m_menubar;
RefPtr<GUI::ToolBarContainer> m_toolbar_container;
RefPtr<GUI::Menu> m_link_context_menu;
RefPtr<GUI::Action> m_link_context_menu_default_action;
URL m_link_context_menu_url;
RefPtr<GUI::Menu> m_image_context_menu;
Gfx::ShareableBitmap m_image_context_menu_bitmap;
URL m_image_context_menu_url;
RefPtr<GUI::Menu> m_tab_context_menu;
RefPtr<GUI::Menu> m_page_context_menu;
String m_title;
RefPtr<const Gfx::Bitmap> m_icon;
bool m_is_history_navigation { false };
};
URL url_from_user_input(const String& input);
}

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "WindowActions.h"
#include <LibGUI/Icon.h>
#include <LibGUI/Window.h>
#include <LibGfx/Bitmap.h>
namespace Browser {
static WindowActions* s_the;
WindowActions& WindowActions::the()
{
ASSERT(s_the);
return *s_the;
}
WindowActions::WindowActions(GUI::Window& window)
{
ASSERT(!s_the);
s_the = this;
m_create_new_tab_action = GUI::Action::create(
"New tab", { Mod_Ctrl, Key_T }, Gfx::Bitmap::load_from_file("/res/icons/16x16/new-tab.png"), [this](auto&) {
if (on_create_new_tab)
on_create_new_tab();
},
&window);
m_next_tab_action = GUI::Action::create(
"Next tab", { Mod_Ctrl, Key_PageDown }, [this](auto&) {
if (on_next_tab)
on_next_tab();
},
&window);
m_previous_tab_action = GUI::Action::create(
"Previous tab", { Mod_Ctrl, Key_PageUp }, [this](auto&) {
if (on_previous_tab)
on_previous_tab();
},
&window);
m_about_action = GUI::Action::create(
"About Browser", GUI::Icon::default_icon("app-browser").bitmap_for_size(16), [this](const GUI::Action&) {
if (on_about)
on_about();
},
&window);
m_show_bookmarks_bar_action = GUI::Action::create_checkable(
"Show bookmarks bar",
[this](auto& action) {
if (on_show_bookmarks_bar)
on_show_bookmarks_bar(action);
},
&window);
}
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <LibGUI/Action.h>
namespace Browser {
class WindowActions {
public:
static WindowActions& the();
WindowActions(GUI::Window&);
Function<void()> on_create_new_tab;
Function<void()> on_next_tab;
Function<void()> on_previous_tab;
Function<void()> on_about;
Function<void(GUI::Action&)> on_show_bookmarks_bar;
GUI::Action& create_new_tab_action() { return *m_create_new_tab_action; }
GUI::Action& next_tab_action() { return *m_next_tab_action; }
GUI::Action& previous_tab_action() { return *m_previous_tab_action; }
GUI::Action& about_action() { return *m_about_action; }
GUI::Action& show_bookmarks_bar_action() { return *m_show_bookmarks_bar_action; }
private:
RefPtr<GUI::Action> m_create_new_tab_action;
RefPtr<GUI::Action> m_next_tab_action;
RefPtr<GUI::Action> m_previous_tab_action;
RefPtr<GUI::Action> m_about_action;
RefPtr<GUI::Action> m_show_bookmarks_bar_action;
};
}

View file

@ -0,0 +1,254 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "BookmarksBarWidget.h"
#include "Browser.h"
#include "InspectorWidget.h"
#include "Tab.h"
#include "WindowActions.h"
#include <AK/StringBuilder.h>
#include <Applications/Browser/BrowserWindowGML.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/ConfigFile.h>
#include <LibCore/File.h>
#include <LibCore/StandardPaths.h>
#include <LibDesktop/Launcher.h>
#include <LibGUI/AboutDialog.h>
#include <LibGUI/Application.h>
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Icon.h>
#include <LibGUI/TabWidget.h>
#include <LibGUI/Window.h>
#include <LibGfx/Bitmap.h>
#include <LibWeb/Loader/ContentFilter.h>
#include <LibWeb/Loader/ResourceLoader.h>
#include <stdio.h>
#include <stdlib.h>
namespace Browser {
String g_home_url;
bool g_multi_process = false;
static String bookmarks_file_path()
{
StringBuilder builder;
builder.append(Core::StandardPaths::config_directory());
builder.append("/bookmarks.json");
return builder.to_string();
}
}
int main(int argc, char** argv)
{
if (getuid() == 0) {
warnln("Refusing to run as root");
return 1;
}
if (pledge("stdio shared_buffer accept unix cpath rpath wpath fattr sendfd recvfd", nullptr) < 0) {
perror("pledge");
return 1;
}
const char* specified_url = nullptr;
Core::ArgsParser args_parser;
args_parser.add_option(Browser::g_multi_process, "Multi-process mode", "multi-process", 'm');
args_parser.add_positional_argument(specified_url, "URL to open", "url", Core::ArgsParser::Required::No);
args_parser.parse(argc, argv);
auto app = GUI::Application::construct(argc, argv);
// Connect to the ProtocolServer immediately so we can drop the "unix" pledge.
Web::ResourceLoader::the();
// Connect to LaunchServer immediately and let it know that we won't ask for anything other than opening
// the user's downloads directory.
// FIXME: This should go away with a standalone download manager at some point.
if (!Desktop::Launcher::add_allowed_url(URL::create_with_file_protocol(Core::StandardPaths::downloads_directory()))
|| !Desktop::Launcher::seal_allowlist()) {
warnln("Failed to set up allowed launch URLs");
return 1;
}
if (pledge("stdio shared_buffer accept unix cpath rpath wpath sendfd recvfd", nullptr) < 0) {
perror("pledge");
return 1;
}
if (unveil("/home", "rwc") < 0) {
perror("unveil");
return 1;
}
if (unveil("/res", "r") < 0) {
perror("unveil");
return 1;
}
if (unveil("/etc/passwd", "r") < 0) {
perror("unveil");
return 1;
}
if (unveil("/tmp/portal/image", "rw") < 0) {
perror("unveil");
return 1;
}
if (unveil("/tmp/portal/webcontent", "rw") < 0) {
perror("unveil");
return 1;
}
unveil(nullptr, nullptr);
auto app_icon = GUI::Icon::default_icon("app-browser");
auto m_config = Core::ConfigFile::get_for_app("Browser");
Browser::g_home_url = m_config->read_entry("Preferences", "Home", "about:blank");
auto ad_filter_list_or_error = Core::File::open(String::formatted("{}/BrowserContentFilters.txt", Core::StandardPaths::config_directory()), Core::IODevice::ReadOnly);
if (!ad_filter_list_or_error.is_error()) {
auto& ad_filter_list = *ad_filter_list_or_error.value();
while (!ad_filter_list.eof()) {
auto line = ad_filter_list.read_line();
if (line.is_empty())
continue;
Web::ContentFilter::the().add_pattern(line);
}
}
bool bookmarksbar_enabled = true;
auto bookmarks_bar = Browser::BookmarksBarWidget::construct(Browser::bookmarks_file_path(), bookmarksbar_enabled);
auto window = GUI::Window::construct();
window->resize(640, 480);
window->set_icon(app_icon.bitmap_for_size(16));
window->set_title("Browser");
auto& widget = window->set_main_widget<GUI::Widget>();
widget.load_from_gml(browser_window_gml);
auto& tab_widget = *widget.find_descendant_of_type_named<GUI::TabWidget>("tab_widget");
auto default_favicon = Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-html.png");
ASSERT(default_favicon);
tab_widget.on_change = [&](auto& active_widget) {
auto& tab = static_cast<Browser::Tab&>(active_widget);
window->set_title(String::formatted("{} - Browser", tab.title()));
tab.did_become_active();
};
tab_widget.on_middle_click = [&](auto& clicked_widget) {
auto& tab = static_cast<Browser::Tab&>(clicked_widget);
tab.on_tab_close_request(tab);
};
tab_widget.on_context_menu_request = [&](auto& clicked_widget, const GUI::ContextMenuEvent& context_menu_event) {
auto& tab = static_cast<Browser::Tab&>(clicked_widget);
tab.context_menu_requested(context_menu_event.screen_position());
};
Browser::WindowActions window_actions(*window);
Function<void(URL url, bool activate)> create_new_tab;
create_new_tab = [&](auto url, auto activate) {
auto type = Browser::g_multi_process ? Browser::Tab::Type::OutOfProcessWebView : Browser::Tab::Type::InProcessWebView;
auto& new_tab = tab_widget.add_tab<Browser::Tab>("New tab", type);
tab_widget.set_bar_visible(!window->is_fullscreen() && tab_widget.children().size() > 1);
tab_widget.set_tab_icon(new_tab, default_favicon);
new_tab.on_title_change = [&](auto title) {
tab_widget.set_tab_title(new_tab, title);
if (tab_widget.active_widget() == &new_tab)
window->set_title(String::formatted("{} - Browser", title));
};
new_tab.on_favicon_change = [&](auto& bitmap) {
tab_widget.set_tab_icon(new_tab, &bitmap);
};
new_tab.on_tab_open_request = [&](auto& url) {
create_new_tab(url, true);
};
new_tab.on_tab_close_request = [&](auto& tab) {
tab_widget.deferred_invoke([&](auto&) {
tab_widget.remove_tab(tab);
tab_widget.set_bar_visible(!window->is_fullscreen() && tab_widget.children().size() > 1);
if (tab_widget.children().is_empty())
app->quit();
});
};
new_tab.load(url);
dbgln("Added new tab {:p}, loading {}", &new_tab, url);
if (activate)
tab_widget.set_active_widget(&new_tab);
};
URL first_url = Browser::g_home_url;
if (specified_url) {
if (Core::File::exists(specified_url)) {
first_url = URL::create_with_file_protocol(Core::File::real_path_for(specified_url));
} else {
first_url = Browser::url_from_user_input(specified_url);
}
}
window_actions.on_create_new_tab = [&] {
create_new_tab(Browser::g_home_url, true);
};
window_actions.on_next_tab = [&] {
tab_widget.activate_next_tab();
};
window_actions.on_previous_tab = [&] {
tab_widget.activate_previous_tab();
};
window_actions.on_about = [&] {
GUI::AboutDialog::show("Browser", app_icon.bitmap_for_size(32), window);
};
window_actions.on_show_bookmarks_bar = [&](auto& action) {
Browser::BookmarksBarWidget::the().set_visible(action.is_checked());
};
window_actions.show_bookmarks_bar_action().set_checked(bookmarksbar_enabled);
create_new_tab(first_url, true);
window->show();
return app->exec();
}