mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 09:00:22 +00:00
Ladybird: Add quick & dirty port of the JS console from Browser :^)
This commit is contained in:
parent
af5250b2cb
commit
aa5f886128
Notes:
sideshowbarker
2024-07-17 02:47:59 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/aa5f886128 Pull-request: https://github.com/SerenityOS/serenity/pull/16583 Reviewed-by: https://github.com/ADKaster Reviewed-by: https://github.com/linusg
6 changed files with 437 additions and 1 deletions
|
@ -52,6 +52,16 @@ BrowserWindow::BrowserWindow(Core::EventLoop& event_loop)
|
|||
}
|
||||
});
|
||||
|
||||
auto* js_console_action = new QAction("Show &JS Console");
|
||||
js_console_action->setIcon(QIcon(QString("%1/res/icons/16x16/filetype-javascript.png").arg(s_serenity_resource_root.characters())));
|
||||
js_console_action->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_J));
|
||||
inspect_menu->addAction(js_console_action);
|
||||
QObject::connect(js_console_action, &QAction::triggered, this, [this] {
|
||||
if (m_current_tab) {
|
||||
m_current_tab->view().show_js_console();
|
||||
}
|
||||
});
|
||||
|
||||
auto* debug_menu = menuBar()->addMenu("&Debug");
|
||||
|
||||
auto* dump_dom_tree_action = new QAction("Dump DOM Tree");
|
||||
|
|
|
@ -35,6 +35,8 @@ find_package(Qt6 REQUIRED COMPONENTS Core Widgets Network)
|
|||
|
||||
set(SOURCES
|
||||
BrowserWindow.cpp
|
||||
ConsoleClient.cpp
|
||||
ConsoleGlobalObject.cpp
|
||||
CookieJar.cpp
|
||||
RequestManagerQt.cpp
|
||||
main.cpp
|
||||
|
|
197
Ladybird/ConsoleClient.cpp
Normal file
197
Ladybird/ConsoleClient.cpp
Normal file
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Brandon Scott <xeon.productions@gmail.com>
|
||||
* Copyright (c) 2020, Hunter Salyer <thefalsehonesty@gmail.com>
|
||||
* Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
|
||||
* Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "ConsoleClient.h"
|
||||
#include "ConsoleGlobalObject.h"
|
||||
#include "WebView.h"
|
||||
#include <LibJS/Interpreter.h>
|
||||
#include <LibJS/MarkupGenerator.h>
|
||||
#include <LibWeb/Bindings/WindowObject.h>
|
||||
#include <LibWeb/HTML/Scripting/ClassicScript.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
|
||||
namespace Ladybird {
|
||||
|
||||
ConsoleClient::ConsoleClient(JS::Console& console, WeakPtr<JS::Interpreter> interpreter, WebView& view)
|
||||
: JS::ConsoleClient(console)
|
||||
, m_view(view)
|
||||
, m_interpreter(interpreter)
|
||||
{
|
||||
JS::DeferGC defer_gc(m_interpreter->heap());
|
||||
|
||||
auto& vm = m_interpreter->vm();
|
||||
auto& global_object = m_interpreter->global_object();
|
||||
|
||||
auto console_global_object = m_interpreter->heap().allocate_without_global_object<ConsoleGlobalObject>(static_cast<Web::Bindings::WindowObject&>(global_object));
|
||||
|
||||
// NOTE: We need to push an execution context here for NativeFunction::create() to succeed during global object initialization.
|
||||
// It gets removed immediately after creating the interpreter in Document::interpreter().
|
||||
auto& eso = verify_cast<Web::HTML::EnvironmentSettingsObject>(*m_interpreter->realm().host_defined());
|
||||
vm.push_execution_context(eso.realm_execution_context());
|
||||
console_global_object->initialize_global_object();
|
||||
vm.pop_execution_context();
|
||||
|
||||
m_console_global_object = JS::make_handle(console_global_object);
|
||||
}
|
||||
|
||||
void ConsoleClient::handle_input(String const& js_source)
|
||||
{
|
||||
auto& settings = verify_cast<Web::HTML::EnvironmentSettingsObject>(*m_interpreter->realm().host_defined());
|
||||
auto script = Web::HTML::ClassicScript::create("(console)", js_source, settings, settings.api_base_url());
|
||||
|
||||
// FIXME: Add parse error printouts back once ClassicScript can report parse errors.
|
||||
|
||||
auto result = script->run();
|
||||
|
||||
StringBuilder output_html;
|
||||
|
||||
if (result.is_abrupt()) {
|
||||
output_html.append("Uncaught exception: "sv);
|
||||
auto error = *result.release_error().value();
|
||||
if (error.is_object())
|
||||
output_html.append(JS::MarkupGenerator::html_from_error(error.as_object()));
|
||||
else
|
||||
output_html.append(JS::MarkupGenerator::html_from_value(error));
|
||||
print_html(output_html.string_view());
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.value().has_value())
|
||||
print_html(JS::MarkupGenerator::html_from_value(*result.value()));
|
||||
}
|
||||
|
||||
void ConsoleClient::print_html(String const& line)
|
||||
{
|
||||
m_message_log.append({ .type = ConsoleOutput::Type::HTML, .data = line });
|
||||
m_view.did_output_js_console_message(m_message_log.size() - 1);
|
||||
}
|
||||
|
||||
void ConsoleClient::clear_output()
|
||||
{
|
||||
m_message_log.append({ .type = ConsoleOutput::Type::Clear, .data = "" });
|
||||
m_view.did_output_js_console_message(m_message_log.size() - 1);
|
||||
}
|
||||
|
||||
void ConsoleClient::begin_group(String const& label, bool start_expanded)
|
||||
{
|
||||
m_message_log.append({ .type = start_expanded ? ConsoleOutput::Type::BeginGroup : ConsoleOutput::Type::BeginGroupCollapsed, .data = label });
|
||||
m_view.did_output_js_console_message(m_message_log.size() - 1);
|
||||
}
|
||||
|
||||
void ConsoleClient::end_group()
|
||||
{
|
||||
m_message_log.append({ .type = ConsoleOutput::Type::EndGroup, .data = "" });
|
||||
m_view.did_output_js_console_message(m_message_log.size() - 1);
|
||||
}
|
||||
|
||||
void ConsoleClient::send_messages(i32 start_index)
|
||||
{
|
||||
// FIXME: Cap the number of messages we send at once?
|
||||
auto messages_to_send = m_message_log.size() - start_index;
|
||||
if (messages_to_send < 1) {
|
||||
// When the console is first created, it requests any messages that happened before
|
||||
// then, by requesting with start_index=0. If we don't have any messages at all, that
|
||||
// is still a valid request, and we can just ignore it.
|
||||
dbgln("Requested non-existent console message index.");
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: Replace with a single Vector of message structs
|
||||
Vector<String> message_types;
|
||||
Vector<String> messages;
|
||||
message_types.ensure_capacity(messages_to_send);
|
||||
messages.ensure_capacity(messages_to_send);
|
||||
|
||||
for (size_t i = start_index; i < m_message_log.size(); i++) {
|
||||
auto& message = m_message_log[i];
|
||||
switch (message.type) {
|
||||
case ConsoleOutput::Type::HTML:
|
||||
message_types.append("html"sv);
|
||||
break;
|
||||
case ConsoleOutput::Type::Clear:
|
||||
message_types.append("clear"sv);
|
||||
break;
|
||||
case ConsoleOutput::Type::BeginGroup:
|
||||
message_types.append("group"sv);
|
||||
break;
|
||||
case ConsoleOutput::Type::BeginGroupCollapsed:
|
||||
message_types.append("groupCollapsed"sv);
|
||||
break;
|
||||
case ConsoleOutput::Type::EndGroup:
|
||||
message_types.append("groupEnd"sv);
|
||||
break;
|
||||
}
|
||||
|
||||
messages.append(message.data);
|
||||
}
|
||||
|
||||
m_view.did_get_js_console_messages(start_index, message_types, messages);
|
||||
}
|
||||
|
||||
void ConsoleClient::clear()
|
||||
{
|
||||
clear_output();
|
||||
}
|
||||
|
||||
// 2.3. Printer(logLevel, args[, options]), https://console.spec.whatwg.org/#printer
|
||||
JS::ThrowCompletionOr<JS::Value> ConsoleClient::printer(JS::Console::LogLevel log_level, PrinterArguments arguments)
|
||||
{
|
||||
if (log_level == JS::Console::LogLevel::Trace) {
|
||||
auto trace = arguments.get<JS::Console::Trace>();
|
||||
StringBuilder html;
|
||||
if (!trace.label.is_empty())
|
||||
html.appendff("<span class='title'>{}</span><br>", escape_html_entities(trace.label));
|
||||
|
||||
html.append("<span class='trace'>"sv);
|
||||
for (auto& function_name : trace.stack)
|
||||
html.appendff("-> {}<br>", escape_html_entities(function_name));
|
||||
html.append("</span>"sv);
|
||||
|
||||
print_html(html.string_view());
|
||||
return JS::js_undefined();
|
||||
}
|
||||
|
||||
if (log_level == JS::Console::LogLevel::Group || log_level == JS::Console::LogLevel::GroupCollapsed) {
|
||||
auto group = arguments.get<JS::Console::Group>();
|
||||
begin_group(group.label, log_level == JS::Console::LogLevel::Group);
|
||||
return JS::js_undefined();
|
||||
}
|
||||
|
||||
auto output = String::join(' ', arguments.get<JS::MarkedVector<JS::Value>>());
|
||||
m_console.output_debug_message(log_level, output);
|
||||
|
||||
StringBuilder html;
|
||||
switch (log_level) {
|
||||
case JS::Console::LogLevel::Debug:
|
||||
html.append("<span class=\"debug\">(d) "sv);
|
||||
break;
|
||||
case JS::Console::LogLevel::Error:
|
||||
html.append("<span class=\"error\">(e) "sv);
|
||||
break;
|
||||
case JS::Console::LogLevel::Info:
|
||||
html.append("<span class=\"info\">(i) "sv);
|
||||
break;
|
||||
case JS::Console::LogLevel::Log:
|
||||
html.append("<span class=\"log\"> "sv);
|
||||
break;
|
||||
case JS::Console::LogLevel::Warn:
|
||||
case JS::Console::LogLevel::CountReset:
|
||||
html.append("<span class=\"warn\">(w) "sv);
|
||||
break;
|
||||
default:
|
||||
html.append("<span>"sv);
|
||||
break;
|
||||
}
|
||||
|
||||
html.append(escape_html_entities(output));
|
||||
html.append("</span>"sv);
|
||||
print_html(html.string_view());
|
||||
return JS::js_undefined();
|
||||
}
|
||||
}
|
114
Ladybird/ConsoleGlobalObject.cpp
Normal file
114
Ladybird/ConsoleGlobalObject.cpp
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
|
||||
* Copyright (c) 2022, Andreas Kling <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "ConsoleGlobalObject.h"
|
||||
#include <LibJS/Runtime/Completion.h>
|
||||
#include <LibWeb/Bindings/NodeWrapper.h>
|
||||
#include <LibWeb/Bindings/NodeWrapperFactory.h>
|
||||
#include <LibWeb/Bindings/WindowObject.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
|
||||
namespace Ladybird {
|
||||
|
||||
ConsoleGlobalObject::ConsoleGlobalObject(Web::Bindings::WindowObject& parent_object)
|
||||
: m_window_object(&parent_object)
|
||||
{
|
||||
}
|
||||
|
||||
void ConsoleGlobalObject::initialize_global_object()
|
||||
{
|
||||
Base::initialize_global_object();
|
||||
|
||||
// $0 magic variable
|
||||
define_native_accessor("$0", inspected_node_getter, nullptr, 0);
|
||||
}
|
||||
|
||||
void ConsoleGlobalObject::visit_edges(Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_window_object);
|
||||
}
|
||||
|
||||
JS::ThrowCompletionOr<JS::Object*> ConsoleGlobalObject::internal_get_prototype_of() const
|
||||
{
|
||||
return m_window_object->internal_get_prototype_of();
|
||||
}
|
||||
|
||||
JS::ThrowCompletionOr<bool> ConsoleGlobalObject::internal_set_prototype_of(JS::Object* prototype)
|
||||
{
|
||||
return m_window_object->internal_set_prototype_of(prototype);
|
||||
}
|
||||
|
||||
JS::ThrowCompletionOr<bool> ConsoleGlobalObject::internal_is_extensible() const
|
||||
{
|
||||
return m_window_object->internal_is_extensible();
|
||||
}
|
||||
|
||||
JS::ThrowCompletionOr<bool> ConsoleGlobalObject::internal_prevent_extensions()
|
||||
{
|
||||
return m_window_object->internal_prevent_extensions();
|
||||
}
|
||||
|
||||
JS::ThrowCompletionOr<Optional<JS::PropertyDescriptor>> ConsoleGlobalObject::internal_get_own_property(JS::PropertyKey const& property_name) const
|
||||
{
|
||||
if (auto result = TRY(m_window_object->internal_get_own_property(property_name)); result.has_value())
|
||||
return result;
|
||||
|
||||
return Base::internal_get_own_property(property_name);
|
||||
}
|
||||
|
||||
JS::ThrowCompletionOr<bool> ConsoleGlobalObject::internal_define_own_property(JS::PropertyKey const& property_name, JS::PropertyDescriptor const& descriptor)
|
||||
{
|
||||
return m_window_object->internal_define_own_property(property_name, descriptor);
|
||||
}
|
||||
|
||||
JS::ThrowCompletionOr<bool> ConsoleGlobalObject::internal_has_property(JS::PropertyKey const& property_name) const
|
||||
{
|
||||
return TRY(Object::internal_has_property(property_name)) || TRY(m_window_object->internal_has_property(property_name));
|
||||
}
|
||||
|
||||
JS::ThrowCompletionOr<JS::Value> ConsoleGlobalObject::internal_get(JS::PropertyKey const& property_name, JS::Value receiver) const
|
||||
{
|
||||
if (TRY(m_window_object->has_own_property(property_name)))
|
||||
return m_window_object->internal_get(property_name, (receiver == this) ? m_window_object : receiver);
|
||||
|
||||
return Base::internal_get(property_name, receiver);
|
||||
}
|
||||
|
||||
JS::ThrowCompletionOr<bool> ConsoleGlobalObject::internal_set(JS::PropertyKey const& property_name, JS::Value value, JS::Value receiver)
|
||||
{
|
||||
return m_window_object->internal_set(property_name, value, (receiver == this) ? m_window_object : receiver);
|
||||
}
|
||||
|
||||
JS::ThrowCompletionOr<bool> ConsoleGlobalObject::internal_delete(JS::PropertyKey const& property_name)
|
||||
{
|
||||
return m_window_object->internal_delete(property_name);
|
||||
}
|
||||
|
||||
JS::ThrowCompletionOr<JS::MarkedVector<JS::Value>> ConsoleGlobalObject::internal_own_property_keys() const
|
||||
{
|
||||
return m_window_object->internal_own_property_keys();
|
||||
}
|
||||
|
||||
JS_DEFINE_NATIVE_FUNCTION(ConsoleGlobalObject::inspected_node_getter)
|
||||
{
|
||||
auto* this_object = TRY(vm.this_value(global_object).to_object(global_object));
|
||||
|
||||
if (!is<ConsoleGlobalObject>(this_object))
|
||||
return vm.throw_completion<JS::TypeError>(global_object, JS::ErrorType::NotAnObjectOfType, "ConsoleGlobalObject");
|
||||
|
||||
auto console_global_object = static_cast<ConsoleGlobalObject*>(this_object);
|
||||
auto& window = console_global_object->m_window_object->impl();
|
||||
auto* inspected_node = window.associated_document().inspected_node();
|
||||
if (!inspected_node)
|
||||
return JS::js_undefined();
|
||||
|
||||
return Web::Bindings::wrap(global_object, *inspected_node);
|
||||
}
|
||||
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
#define AK_DONT_REPLACE_STD
|
||||
|
||||
#include "WebView.h"
|
||||
#include "ConsoleClient.h"
|
||||
#include "CookieJar.h"
|
||||
#include "RequestManagerQt.h"
|
||||
#include <AK/Assertions.h>
|
||||
|
@ -30,12 +31,14 @@
|
|||
#include <LibGfx/ImageDecoder.h>
|
||||
#include <LibGfx/PNGWriter.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <LibJS/Interpreter.h>
|
||||
#include <LibMain/Main.h>
|
||||
#include <LibWeb/Bindings/MainThreadVM.h>
|
||||
#include <LibWeb/Cookie/ParsedCookie.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/Dump.h>
|
||||
#include <LibWeb/HTML/BrowsingContext.h>
|
||||
#include <LibWeb/HTML/Scripting/ClassicScript.h>
|
||||
#include <LibWeb/HTML/Storage.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
#include <LibWeb/ImageDecoding.h>
|
||||
|
@ -50,12 +53,26 @@
|
|||
#include <LibWebSocket/WebSocket.h>
|
||||
#include <QCursor>
|
||||
#include <QIcon>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QMouseEvent>
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include <QScrollBar>
|
||||
#include <QTextEdit>
|
||||
#include <QVBoxLayout>
|
||||
#include <stdlib.h>
|
||||
|
||||
AK::String akstring_from_qstring(QString const& qstring)
|
||||
{
|
||||
return AK::String(qstring.toUtf8().data());
|
||||
}
|
||||
|
||||
QString qstring_from_akstring(AK::String const& akstring)
|
||||
{
|
||||
return QString::fromUtf8(akstring.characters(), akstring.length());
|
||||
}
|
||||
|
||||
String s_serenity_resource_root = [] {
|
||||
auto const* source_dir = getenv("SERENITY_SOURCE_DIR");
|
||||
if (source_dir) {
|
||||
|
@ -162,6 +179,20 @@ public:
|
|||
|
||||
virtual void page_did_finish_loading(AK::URL const&) override
|
||||
{
|
||||
initialize_js_console();
|
||||
m_console_client->send_messages(0);
|
||||
}
|
||||
|
||||
void initialize_js_console()
|
||||
{
|
||||
auto* document = page().top_level_browsing_context().active_document();
|
||||
auto interpreter = document->interpreter().make_weak_ptr();
|
||||
if (m_interpreter.ptr() == interpreter.ptr())
|
||||
return;
|
||||
|
||||
m_interpreter = interpreter;
|
||||
m_console_client = make<Ladybird::ConsoleClient>(interpreter->global_object().console(), interpreter, m_view);
|
||||
interpreter->global_object().console().set_client(*m_console_client.ptr());
|
||||
}
|
||||
|
||||
virtual void page_did_change_selection() override
|
||||
|
@ -290,7 +321,6 @@ public:
|
|||
|
||||
void set_should_show_line_box_borders(bool state) { m_should_show_line_box_borders = state; }
|
||||
|
||||
private:
|
||||
HeadlessBrowserPageClient(WebView& view)
|
||||
: m_view(view)
|
||||
, m_page(make<Web::Page>(*this))
|
||||
|
@ -301,6 +331,8 @@ private:
|
|||
NonnullOwnPtr<Web::Page> m_page;
|
||||
Browser::CookieJar m_cookie_jar;
|
||||
|
||||
OwnPtr<Ladybird::ConsoleClient> m_console_client;
|
||||
WeakPtr<JS::Interpreter> m_interpreter;
|
||||
RefPtr<Gfx::PaletteImpl> m_palette_impl;
|
||||
Gfx::IntRect m_viewport_rect { 0, 0, 800, 600 };
|
||||
Web::CSS::PreferredColorScheme m_preferred_color_scheme { Web::CSS::PreferredColorScheme::Auto };
|
||||
|
@ -675,3 +707,69 @@ String WebView::source() const
|
|||
return String::empty();
|
||||
return document->source();
|
||||
}
|
||||
|
||||
void WebView::run_javascript(String const& js_source) const
|
||||
{
|
||||
auto* active_document = const_cast<Web::DOM::Document*>(m_page_client->page().top_level_browsing_context().active_document());
|
||||
|
||||
if (!active_document)
|
||||
return;
|
||||
|
||||
// This is partially based on "execute a javascript: URL request" https://html.spec.whatwg.org/multipage/browsing-the-web.html#javascript-protocol
|
||||
|
||||
// Let settings be browsingContext's active document's relevant settings object.
|
||||
auto& settings = active_document->relevant_settings_object();
|
||||
|
||||
// Let baseURL be settings's API base URL.
|
||||
auto base_url = settings.api_base_url();
|
||||
|
||||
// Let script be the result of creating a classic script given scriptSource, settings, baseURL, and the default classic script fetch options.
|
||||
// FIXME: This doesn't pass in "default classic script fetch options"
|
||||
// FIXME: What should the filename be here?
|
||||
auto script = Web::HTML::ClassicScript::create("(client connection run_javascript)", js_source, settings, base_url);
|
||||
|
||||
// Let evaluationStatus be the result of running the classic script script.
|
||||
auto evaluation_status = script->run();
|
||||
|
||||
if (evaluation_status.is_error())
|
||||
dbgln("Exception :(");
|
||||
}
|
||||
|
||||
void WebView::did_output_js_console_message(i32 message_index)
|
||||
{
|
||||
m_page_client->m_console_client->send_messages(message_index);
|
||||
}
|
||||
|
||||
void WebView::did_get_js_console_messages(i32, Vector<String>, Vector<String> messages)
|
||||
{
|
||||
for (auto& message : messages) {
|
||||
m_js_console_output_edit->append(qstring_from_akstring(message).trimmed());
|
||||
}
|
||||
}
|
||||
|
||||
void WebView::show_js_console()
|
||||
{
|
||||
if (!m_js_console_widget) {
|
||||
m_js_console_widget = new QWidget;
|
||||
m_js_console_widget->setWindowTitle("JS Console");
|
||||
auto* layout = new QVBoxLayout;
|
||||
m_js_console_widget->setLayout(layout);
|
||||
m_js_console_output_edit = new QTextEdit;
|
||||
m_js_console_output_edit->setReadOnly(true);
|
||||
m_js_console_input_edit = new QLineEdit;
|
||||
layout->addWidget(m_js_console_output_edit);
|
||||
layout->addWidget(m_js_console_input_edit);
|
||||
m_js_console_widget->resize(640, 480);
|
||||
|
||||
QObject::connect(m_js_console_input_edit, &QLineEdit::returnPressed, [this] {
|
||||
auto code = m_js_console_input_edit->text().trimmed();
|
||||
m_js_console_input_edit->clear();
|
||||
|
||||
m_js_console_output_edit->append(QString("> %1").arg(code));
|
||||
|
||||
m_page_client->m_console_client->handle_input(akstring_from_qstring(code));
|
||||
});
|
||||
}
|
||||
m_js_console_widget->show();
|
||||
m_js_console_input_edit->setFocus();
|
||||
}
|
||||
|
|
|
@ -12,6 +12,10 @@
|
|||
#include <AK/String.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
#include <QAbstractScrollArea>
|
||||
#include <QPointer>
|
||||
|
||||
class QTextEdit;
|
||||
class QLineEdit;
|
||||
|
||||
class HeadlessBrowserPageClient;
|
||||
|
||||
|
@ -34,6 +38,13 @@ public:
|
|||
|
||||
String source() const;
|
||||
|
||||
void run_javascript(String const& js_source) const;
|
||||
|
||||
void did_output_js_console_message(i32 message_index);
|
||||
void did_get_js_console_messages(i32 start_index, Vector<String> message_types, Vector<String> messages);
|
||||
|
||||
void show_js_console();
|
||||
|
||||
signals:
|
||||
void linkHovered(QString, int timeout = 0);
|
||||
void linkUnhovered();
|
||||
|
@ -48,4 +59,8 @@ private:
|
|||
|
||||
qreal m_inverse_pixel_scaling_ratio { 1.0 };
|
||||
bool m_should_show_line_box_borders { false };
|
||||
|
||||
QPointer<QWidget> m_js_console_widget;
|
||||
QTextEdit* m_js_console_output_edit { nullptr };
|
||||
QLineEdit* m_js_console_input_edit { nullptr };
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue