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:
parent
aa939c4b4b
commit
dc28c07fa5
287 changed files with 1 additions and 1 deletions
241
Userland/Applications/Browser/BookmarksBarWidget.cpp
Normal file
241
Userland/Applications/Browser/BookmarksBarWidget.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
81
Userland/Applications/Browser/BookmarksBarWidget.h
Normal file
81
Userland/Applications/Browser/BookmarksBarWidget.h
Normal 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 };
|
||||
};
|
||||
|
||||
}
|
35
Userland/Applications/Browser/Browser.h
Normal file
35
Userland/Applications/Browser/Browser.h
Normal 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;
|
||||
|
||||
}
|
130
Userland/Applications/Browser/BrowserConsoleClient.cpp
Normal file
130
Userland/Applications/Browser/BrowserConsoleClient.cpp
Normal 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 = "<anonymous>";
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
60
Userland/Applications/Browser/BrowserConsoleClient.h
Normal file
60
Userland/Applications/Browser/BrowserConsoleClient.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
15
Userland/Applications/Browser/BrowserWindow.gml
Normal file
15
Userland/Applications/Browser/BrowserWindow.gml
Normal 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"
|
||||
}
|
||||
}
|
19
Userland/Applications/Browser/CMakeLists.txt
Normal file
19
Userland/Applications/Browser/CMakeLists.txt
Normal 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)
|
169
Userland/Applications/Browser/ConsoleWidget.cpp
Normal file
169
Userland/Applications/Browser/ConsoleWidget.cpp
Normal 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("> ");
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
57
Userland/Applications/Browser/ConsoleWidget.h
Normal file
57
Userland/Applications/Browser/ConsoleWidget.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
182
Userland/Applications/Browser/DownloadWidget.cpp
Normal file
182
Userland/Applications/Browser/DownloadWidget.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
61
Userland/Applications/Browser/DownloadWidget.h
Normal file
61
Userland/Applications/Browser/DownloadWidget.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
73
Userland/Applications/Browser/History.cpp
Normal file
73
Userland/Applications/Browser/History.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
54
Userland/Applications/Browser/History.h
Normal file
54
Userland/Applications/Browser/History.h
Normal 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 };
|
||||
};
|
||||
|
||||
}
|
94
Userland/Applications/Browser/InspectorWidget.cpp
Normal file
94
Userland/Applications/Browser/InspectorWidget.cpp
Normal 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));
|
||||
}
|
||||
|
||||
}
|
53
Userland/Applications/Browser/InspectorWidget.h
Normal file
53
Userland/Applications/Browser/InspectorWidget.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
550
Userland/Applications/Browser/Tab.cpp
Normal file
550
Userland/Applications/Browser/Tab.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
22
Userland/Applications/Browser/Tab.gml
Normal file
22
Userland/Applications/Browser/Tab.gml
Normal 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"
|
||||
}
|
||||
}
|
123
Userland/Applications/Browser/Tab.h
Normal file
123
Userland/Applications/Browser/Tab.h
Normal 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);
|
||||
|
||||
}
|
82
Userland/Applications/Browser/WindowActions.cpp
Normal file
82
Userland/Applications/Browser/WindowActions.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
59
Userland/Applications/Browser/WindowActions.h
Normal file
59
Userland/Applications/Browser/WindowActions.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
254
Userland/Applications/Browser/main.cpp
Normal file
254
Userland/Applications/Browser/main.cpp
Normal 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();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue