Skip to content

Commit

Permalink
Inspector: Add a basic style sheet inspector
Browse files Browse the repository at this point in the history
Choosing options from the `<select>` will load and display that style
sheet's source text, with some checks to make sure that the text that
just loaded is the one we currently want.

The UI is a little goofy when scrolling, as it uses `position: sticky`
which we don't implement yet. But that's just more motivation to
implement it! :^)

(cherry picked from commit da171c3230caaee53213d0dd04007c9b4343e3e2)
  • Loading branch information
AtkinsSJ authored and nico committed Nov 16, 2024
1 parent 4cf75e5 commit 2b22369
Show file tree
Hide file tree
Showing 18 changed files with 270 additions and 2 deletions.
28 changes: 27 additions & 1 deletion Base/res/ladybird/inspector.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
:root {
--code-font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}

@media (prefers-color-scheme: dark) {
:root {
--background: rgb(23, 23, 23);
Expand Down Expand Up @@ -174,6 +178,17 @@ body {
overflow: auto scroll;
}

.tab-header {
position: sticky;
top: 2px; /* FIXME: Remove this when https://github.com/LadybirdBrowser/ladybird/issues/1245 is resolved. */
left: 0;
right: 0;
background-color: var(--tab-controls);
border-top: 2px solid var(--background);
display: flex;
padding: 0.5em;
}

details > :not(:first-child) {
display: list-item;
list-style: none inside;
Expand Down Expand Up @@ -204,7 +219,7 @@ details > :not(:first-child) {
}

.console {
font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-family: var(--code-font-family);
width: 100%;
height: 100%;
}
Expand Down Expand Up @@ -330,3 +345,14 @@ details > :not(:first-child) {
padding: 4px;
text-align: left;
}

#style-sheet-picker {
flex-grow: 1;
}

#style-sheet-source {
font-size: 10pt;
font-family: var(--code-font-family);
white-space: pre;
padding: 0.5em;
}
91 changes: 91 additions & 0 deletions Base/res/ladybird/inspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,97 @@ inspector.addAttributeToDOMNodeID = nodeID => {
pendingEditDOMNode = null;
};

inspector.setStyleSheets = styleSheets => {
const styleSheetPicker = document.getElementById("style-sheet-picker");
const styleSheetSource = document.getElementById("style-sheet-source");
styleSheetPicker.replaceChildren();
styleSheetSource.innerHTML = "";

function addOption(styleSheet, text) {
const option = document.createElement("option");
option.innerText = text;
if (styleSheet.type) {
option.dataset["type"] = styleSheet.type;
}
if (styleSheet.domNodeId) {
option.dataset["domNodeId"] = styleSheet.domNodeId;
}
if (styleSheet.url) {
option.dataset["url"] = styleSheet.url;
}
styleSheetPicker.add(option);
}

if (styleSheets.length > 0) {
let styleElementIndex = 1;
for (const styleSheet of styleSheets) {
switch (styleSheet.type) {
case "StyleElement":
addOption(styleSheet, `Style element #${styleElementIndex++}`);
break;
case "LinkElement":
addOption(styleSheet, styleSheet.url);
break;
case "ImportRule":
addOption(styleSheet, styleSheet.url);
break;
case "UserAgent":
addOption(styleSheet, `User agent: ${styleSheet.url}`);
break;
case "UserStyle":
addOption(styleSheet, "User style");
break;
}
}
styleSheetPicker.disabled = false;
} else {
addOption({}, "No style sheets found");
styleSheetPicker.disabled = true;
}

styleSheetPicker.selectedIndex = 0;

if (!styleSheetPicker.disabled) {
loadStyleSheet();
}
};

const loadStyleSheet = () => {
const styleSheetPicker = document.getElementById("style-sheet-picker");
const styleSheetSource = document.getElementById("style-sheet-source");
const selectedOption = styleSheetPicker.selectedOptions[0];

styleSheetSource.innerHTML = "Loading...";
inspector.requestStyleSheetSource(
selectedOption.dataset["type"],
selectedOption.dataset["domNodeId"],
selectedOption.dataset["url"]
);
};

inspector.setStyleSheetSource = (identifier, sourceBase64) => {
const styleSheetPicker = document.getElementById("style-sheet-picker");
const styleSheetSource = document.getElementById("style-sheet-source");
const selectedOption = styleSheetPicker.selectedOptions[0];

// Make sure this is the source for the currently-selected style sheet.
// NOTE: These are != not !== intentionally.
if (
identifier.type != selectedOption.dataset["type"] ||
identifier.domNodeId != selectedOption.dataset["domNodeId"] ||
identifier.url != selectedOption.dataset["url"]
) {
console.log(
JSON.stringify(identifier),
"doesn't match",
JSON.stringify(selectedOption.dataset)
);
return;
}

styleSheetSource.innerHTML = decodeBase64(sourceBase64);
};

inspector.createPropertyTables = (computedStyle, resolvedStyle, customProperties) => {
const createPropertyTable = (tableID, properties) => {
let oldTable = document.getElementById(tableID);
Expand Down
4 changes: 4 additions & 0 deletions Ladybird/Qt/Tab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,10 @@ Tab::Tab(BrowserWindow* window, WebContentOptions const& web_content_options, St
m_window->new_tab_from_content(html, Web::HTML::ActivateTab::Yes);
};

view().on_inspector_requested_style_sheet_source = [this](auto const& identifier) {
view().request_style_sheet_source(identifier);
};

view().on_navigate_back = [this]() {
back();
};
Expand Down
13 changes: 13 additions & 0 deletions Userland/Libraries/LibWeb/Internals/Inspector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <LibWeb/Bindings/InspectorPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CSS/Selector.h>
#include <LibWeb/CSS/StyleSheetIdentifier.h>
#include <LibWeb/DOM/NamedNodeMap.h>
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/HTML/Window.h>
Expand Down Expand Up @@ -76,6 +77,18 @@ void Inspector::request_dom_tree_context_menu(i32 node_id, i32 client_x, i32 cli
inspector_page_client().inspector_did_request_dom_tree_context_menu(node_id, { client_x, client_y }, type, tag, attribute_index.map([](auto index) { return static_cast<size_t>(index); }));
}

void Inspector::request_style_sheet_source(String const& type_string, Optional<i32> const& dom_node_unique_id, Optional<String> const& url)
{
auto type = CSS::style_sheet_identifier_type_from_string(type_string);
VERIFY(type.has_value());

inspector_page_client().inspector_did_request_style_sheet_source({
.type = type.value(),
.dom_element_unique_id = dom_node_unique_id,
.url = url,
});
}

void Inspector::execute_console_script(String const& script)
{
inspector_page_client().inspector_did_execute_console_script(script);
Expand Down
2 changes: 2 additions & 0 deletions Userland/Libraries/LibWeb/Internals/Inspector.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class Inspector final : public Bindings::PlatformObject {

void request_dom_tree_context_menu(i32 node_id, i32 client_x, i32 client_y, String const& type, Optional<String> const& tag, Optional<WebIDL::UnsignedLongLong> const& attribute_index);

void request_style_sheet_source(String const& type, Optional<i32> const& dom_node_unique_id, Optional<String> const& url);

void execute_console_script(String const& script);

void export_inspector_html(String const& html);
Expand Down
2 changes: 2 additions & 0 deletions Userland/Libraries/LibWeb/Internals/Inspector.idl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

undefined requestDOMTreeContextMenu(long nodeID, long clientX, long clientY, DOMString type, DOMString? tag, unsigned long long? attributeIndex);

undefined requestStyleSheetSource(DOMString type, long? domNodeID, DOMString? url);

undefined executeConsoleScript(DOMString script);

undefined exportInspectorHTML(DOMString html);
Expand Down
2 changes: 2 additions & 0 deletions Userland/Libraries/LibWeb/Page/Page.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <LibWeb/CSS/PreferredContrast.h>
#include <LibWeb/CSS/PreferredMotion.h>
#include <LibWeb/CSS/Selector.h>
#include <LibWeb/CSS/StyleSheetIdentifier.h>
#include <LibWeb/Cookie/Cookie.h>
#include <LibWeb/Forward.h>
#include <LibWeb/HTML/ActivateTab.h>
Expand Down Expand Up @@ -382,6 +383,7 @@ class PageClient : public JS::Cell {
virtual void inspector_did_add_dom_node_attributes([[maybe_unused]] i32 node_id, [[maybe_unused]] JS::NonnullGCPtr<DOM::NamedNodeMap> attributes) { }
virtual void inspector_did_replace_dom_node_attribute([[maybe_unused]] i32 node_id, [[maybe_unused]] size_t attribute_index, [[maybe_unused]] JS::NonnullGCPtr<DOM::NamedNodeMap> replacement_attributes) { }
virtual void inspector_did_request_dom_tree_context_menu([[maybe_unused]] i32 node_id, [[maybe_unused]] CSSPixelPoint position, [[maybe_unused]] String const& type, [[maybe_unused]] Optional<String> const& tag, [[maybe_unused]] Optional<size_t> const& attribute_index) { }
virtual void inspector_did_request_style_sheet_source([[maybe_unused]] CSS::StyleSheetIdentifier const& identifier) { }
virtual void inspector_did_execute_console_script([[maybe_unused]] String const& script) { }
virtual void inspector_did_export_inspector_html([[maybe_unused]] String const& html) { }

Expand Down
44 changes: 44 additions & 0 deletions Userland/Libraries/LibWebView/InspectorClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ static ErrorOr<JsonValue> parse_json_tree(StringView json)
return parsed_tree;
}

static String style_sheet_identifier_to_json(Web::CSS::StyleSheetIdentifier const& identifier)
{
return MUST(String::formatted("{{ type: '{}', domNodeId: {}, url: '{}' }}"sv,
Web::CSS::style_sheet_identifier_type_to_string(identifier.type),
identifier.dom_element_unique_id.map([](auto& it) { return MUST(String::number(it)); }).value_or("undefined"_string),
identifier.url.value_or("undefined"_string)));
}

InspectorClient::InspectorClient(ViewImplementation& content_web_view, ViewImplementation& inspector_web_view)
: m_content_web_view(content_web_view)
, m_inspector_web_view(inspector_web_view)
Expand Down Expand Up @@ -105,6 +113,26 @@ InspectorClient::InspectorClient(ViewImplementation& content_web_view, ViewImple
select_node(node_id);
};

m_content_web_view.on_received_style_sheet_list = [this](auto const& style_sheets) {
StringBuilder builder;
builder.append("inspector.setStyleSheets(["sv);
for (auto& style_sheet : style_sheets) {
builder.appendff("{}, "sv, style_sheet_identifier_to_json(style_sheet));
}
builder.append("]);"sv);

m_inspector_web_view.run_javascript(builder.string_view());
};

m_content_web_view.on_received_style_sheet_source = [this](Web::CSS::StyleSheetIdentifier const& identifier, String const& source) {
// TODO: Highlight it
auto escaped_source = escape_html_entities(source.bytes()).replace("\t"sv, " "sv, ReplaceMode::All);
auto script = MUST(String::formatted("inspector.setStyleSheetSource({}, \"{}\");",
style_sheet_identifier_to_json(identifier),
MUST(encode_base64(escaped_source.bytes()))));
m_inspector_web_view.run_javascript(script);
};

m_content_web_view.on_finshed_editing_dom_node = [this](auto const& node_id) {
m_pending_selection = node_id;
m_dom_tree_loaded = false;
Expand Down Expand Up @@ -181,6 +209,10 @@ InspectorClient::InspectorClient(ViewImplementation& content_web_view, ViewImple
m_content_web_view.replace_dom_node_attribute(node_id, attribute.name, replacement_attributes);
};

m_inspector_web_view.on_inspector_requested_style_sheet_source = [this](auto const& identifier) {
m_content_web_view.request_style_sheet_source(identifier);
};

m_inspector_web_view.on_inspector_executed_console_script = [this](auto const& script) {
append_console_source(script);

Expand Down Expand Up @@ -241,6 +273,8 @@ InspectorClient::~InspectorClient()
m_content_web_view.on_received_dom_node_properties = nullptr;
m_content_web_view.on_received_dom_tree = nullptr;
m_content_web_view.on_received_hovered_node_id = nullptr;
m_content_web_view.on_received_style_sheet_list = nullptr;
m_content_web_view.on_inspector_requested_style_sheet_source = nullptr;
}

void InspectorClient::inspect()
Expand All @@ -250,6 +284,7 @@ void InspectorClient::inspect()

m_content_web_view.inspect_dom_tree();
m_content_web_view.inspect_accessibility_tree();
m_content_web_view.list_style_sheets();
}

void InspectorClient::reset()
Expand Down Expand Up @@ -420,13 +455,22 @@ void InspectorClient::load_inspector()
<div class="tab-controls">
<button id="dom-tree-button" onclick="selectTopTab(this, 'dom-tree')">DOM Tree</button>
<button id="accessibility-tree-button" onclick="selectTopTab(this, 'accessibility-tree')">Accessibility Tree</button>
<button id="style-sheets-button" onclick="selectTopTab(this, 'style-sheets')">Style Sheets</button>
</div>
<div class="global-controls">
<button id="export-inspector-button" title="Export the Inspector to an HTML file" onclick="inspector.exportInspector()"></button>
</div>
</div>
<div id="dom-tree" class="tab-content html"></div>
<div id="accessibility-tree" class="tab-content"></div>
<div id="style-sheets" class="tab-content" style="padding: 0">
<div class="tab-header">
<select id="style-sheet-picker" disabled onchange="loadStyleSheet()">
<option value="." selected>No style sheets found</option>
</select>
</div>
<div id="style-sheet-source"></div>
</div>
</div>
<div id="inspector-separator" class="split-view-separator">
<svg viewBox="0 0 16 5" xmlns="http://www.w3.org/2000/svg">
Expand Down
10 changes: 10 additions & 0 deletions Userland/Libraries/LibWebView/ViewImplementation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,16 @@ void ViewImplementation::get_dom_node_html(i32 node_id)
client().async_get_dom_node_html(page_id(), node_id);
}

void ViewImplementation::list_style_sheets()
{
client().async_list_style_sheets(page_id());
}

void ViewImplementation::request_style_sheet_source(Web::CSS::StyleSheetIdentifier const& identifier)
{
client().async_request_style_sheet_source(page_id(), identifier);
}

void ViewImplementation::debug_request(ByteString const& request, ByteString const& argument)
{
client().async_debug_request(page_id(), request, argument);
Expand Down
6 changes: 6 additions & 0 deletions Userland/Libraries/LibWebView/ViewImplementation.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ class ViewImplementation {
void remove_dom_node(i32 node_id);
void get_dom_node_html(i32 node_id);

void list_style_sheets();
void request_style_sheet_source(Web::CSS::StyleSheetIdentifier const&);

void debug_request(ByteString const& request, ByteString const& argument = {});

void run_javascript(StringView);
Expand Down Expand Up @@ -181,6 +184,9 @@ class ViewImplementation {
Function<void(ByteString const&)> on_received_dom_tree;
Function<void(Optional<DOMNodeProperties>)> on_received_dom_node_properties;
Function<void(ByteString const&)> on_received_accessibility_tree;
Function<void(Vector<Web::CSS::StyleSheetIdentifier>)> on_received_style_sheet_list;
Function<void(Web::CSS::StyleSheetIdentifier const&)> on_inspector_requested_style_sheet_source;
Function<void(Web::CSS::StyleSheetIdentifier const&, String const&)> on_received_style_sheet_source;
Function<void(i32 node_id)> on_received_hovered_node_id;
Function<void(Optional<i32> const& node_id)> on_finshed_editing_dom_node;
Function<void(String const&)> on_received_dom_node_html;
Expand Down
24 changes: 24 additions & 0 deletions Userland/Libraries/LibWebView/WebContentClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,30 @@ Messages::WebContentClient::RequestWorkerAgentResponse WebContentClient::request
return IPC::File {};
}

void WebContentClient::inspector_did_list_style_sheets(u64 page_id, Vector<Web::CSS::StyleSheetIdentifier> const& stylesheets)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_received_style_sheet_list)
view->on_received_style_sheet_list(stylesheets);
}
}

void WebContentClient::inspector_did_request_style_sheet_source(u64 page_id, Web::CSS::StyleSheetIdentifier const& identifier)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_inspector_requested_style_sheet_source)
view->on_inspector_requested_style_sheet_source(identifier);
}
}

void WebContentClient::did_request_style_sheet_source(u64 page_id, Web::CSS::StyleSheetIdentifier const& identifier, String const& source)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_received_style_sheet_source)
view->on_received_style_sheet_source(identifier, source);
}
}

Optional<ViewImplementation&> WebContentClient::view_for_page_id(u64 page_id, SourceLocation location)
{
if (auto view = m_views.get(page_id); view.has_value())
Expand Down
4 changes: 4 additions & 0 deletions Userland/Libraries/LibWebView/WebContentClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <AK/HashMap.h>
#include <AK/SourceLocation.h>
#include <LibIPC/ConnectionToServer.h>
#include <LibWeb/CSS/StyleSheetIdentifier.h>
#include <LibWeb/HTML/ActivateTab.h>
#include <LibWeb/HTML/FileFilter.h>
#include <LibWeb/HTML/SelectItem.h>
Expand Down Expand Up @@ -114,6 +115,9 @@ class WebContentClient final
virtual void inspector_did_execute_console_script(u64 page_id, String const& script) override;
virtual void inspector_did_export_inspector_html(u64 page_id, String const& html) override;
virtual Messages::WebContentClient::RequestWorkerAgentResponse request_worker_agent(u64 page_id) override;
virtual void inspector_did_list_style_sheets(u64 page_id, Vector<Web::CSS::StyleSheetIdentifier> const& stylesheets) override;
virtual void inspector_did_request_style_sheet_source(u64 page_id, Web::CSS::StyleSheetIdentifier const& identifier) override;
virtual void did_request_style_sheet_source(u64 page_id, Web::CSS::StyleSheetIdentifier const& identifier, String const& source) override;

Optional<ViewImplementation&> view_for_page_id(u64, SourceLocation = SourceLocation::current());

Expand Down
Loading

0 comments on commit 2b22369

Please sign in to comment.