mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 07:30:19 +00:00
LibWebView: Allow editing the DOM through the Inspector WebView
This allows a limited amount of DOM manipulation through the Inspector. Users may edit node tag names, text content, and attributes. To initiate an edit, double-click the tag/text/attribute of interest. To remove an attribute, begin editing the attribute and remove all of its text. To add an attribute, begin editing an existing attribute and add the new attribute's text before or after the existing attribute's text. This isn't going to be the final UX, but works for now just as a consequence of how attribute changes are implemented. A future patch will add more explicit add/delete actions.
This commit is contained in:
parent
1236cbd41a
commit
6d743ce9e8
Notes:
sideshowbarker
2024-07-17 02:57:43 +09:00
Author: https://github.com/trflynn89 Commit: https://github.com/SerenityOS/serenity/commit/6d743ce9e8 Pull-request: https://github.com/SerenityOS/serenity/pull/22150 Reviewed-by: https://github.com/awesomekling ✅
3 changed files with 97 additions and 3 deletions
|
@ -137,6 +137,11 @@ details > :not(:first-child) {
|
|||
padding: 1px;
|
||||
}
|
||||
|
||||
.dom-editor {
|
||||
width: fit-content;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.hoverable:hover {
|
||||
background-color: #31383e;
|
||||
|
|
|
@ -96,6 +96,15 @@ inspector.loadDOMTree = tree => {
|
|||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
domNodes = domTree.querySelectorAll(".editable");
|
||||
|
||||
for (let domNode of domNodes) {
|
||||
domNode.addEventListener("dblclick", event => {
|
||||
editDOMNode(domNode);
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
inspector.loadAccessibilityTree = tree => {
|
||||
|
@ -166,6 +175,64 @@ const inspectDOMNode = domNode => {
|
|||
inspector.inspectDOMNode(domNode.dataset.id, domNode.dataset.pseudoElement);
|
||||
};
|
||||
|
||||
const editDOMNode = domNode => {
|
||||
if (selectedDOMNode === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const domNodeID = selectedDOMNode.dataset.id;
|
||||
const type = domNode.dataset.nodeType;
|
||||
|
||||
selectedDOMNode.classList.remove("selected");
|
||||
|
||||
let input = document.createElement("input");
|
||||
input.classList.add("dom-editor");
|
||||
input.classList.add("selected");
|
||||
input.value = domNode.innerText;
|
||||
|
||||
const handleChange = () => {
|
||||
input.removeEventListener("change", handleChange);
|
||||
input.removeEventListener("blur", cancelChange);
|
||||
|
||||
if (type === "text" || type === "comment") {
|
||||
inspector.setDOMNodeText(domNodeID, input.value);
|
||||
} else if (type === "tag") {
|
||||
try {
|
||||
const element = document.createElement(input.value);
|
||||
inspector.setDOMNodeTag(domNodeID, input.value);
|
||||
} catch {
|
||||
cancelChange();
|
||||
}
|
||||
} else if (type === "attribute") {
|
||||
let element = document.createElement("div");
|
||||
element.innerHTML = `<div ${input.value}></div>`;
|
||||
|
||||
inspector.replaceDOMNodeAttribute(
|
||||
domNodeID,
|
||||
domNode.dataset.attributeName,
|
||||
element.children[0].attributes
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const cancelChange = () => {
|
||||
selectedDOMNode.classList.add("selected");
|
||||
input.parentNode.replaceChild(domNode, input);
|
||||
};
|
||||
|
||||
input.addEventListener("change", handleChange);
|
||||
input.addEventListener("blur", cancelChange);
|
||||
|
||||
domNode.parentNode.replaceChild(input, domNode);
|
||||
|
||||
setTimeout(() => {
|
||||
input.focus();
|
||||
|
||||
// FIXME: Invoke `select` when it isn't just stubbed out.
|
||||
// input.select();
|
||||
});
|
||||
};
|
||||
|
||||
const executeConsoleScript = consoleInput => {
|
||||
const script = consoleInput.value;
|
||||
|
||||
|
|
|
@ -109,6 +109,25 @@ InspectorClient::InspectorClient(ViewImplementation& content_web_view, ViewImple
|
|||
m_inspector_web_view.run_javascript(builder.string_view());
|
||||
};
|
||||
|
||||
m_inspector_web_view.on_inspector_set_dom_node_text = [this](auto node_id, auto const& text) {
|
||||
m_content_web_view.set_dom_node_text(node_id, text);
|
||||
|
||||
m_pending_selection = node_id;
|
||||
inspect();
|
||||
};
|
||||
|
||||
m_inspector_web_view.on_inspector_set_dom_node_tag = [this](auto node_id, auto const& tag) {
|
||||
m_pending_selection = m_content_web_view.set_dom_node_tag(node_id, tag);
|
||||
inspect();
|
||||
};
|
||||
|
||||
m_inspector_web_view.on_inspector_replaced_dom_node_attribute = [this](auto node_id, auto const& name, auto const& replacement_attributes) {
|
||||
m_content_web_view.replace_dom_node_attribute(node_id, name, replacement_attributes);
|
||||
|
||||
m_pending_selection = node_id;
|
||||
inspect();
|
||||
};
|
||||
|
||||
m_inspector_web_view.on_inspector_executed_console_script = [this](auto const& script) {
|
||||
append_console_source(script);
|
||||
|
||||
|
@ -128,6 +147,7 @@ InspectorClient::~InspectorClient()
|
|||
|
||||
void InspectorClient::inspect()
|
||||
{
|
||||
m_dom_tree_loaded = false;
|
||||
m_content_web_view.inspect_dom_tree();
|
||||
m_content_web_view.inspect_accessibility_tree();
|
||||
}
|
||||
|
@ -326,7 +346,7 @@ String InspectorClient::generate_dom_tree(JsonObject const& dom_tree)
|
|||
builder.append(name);
|
||||
builder.append("</span>"sv);
|
||||
} else {
|
||||
builder.appendff("<span class=\"hoverable\" {}>", data_attributes.string_view());
|
||||
builder.appendff("<span data-node-type=\"text\" class=\"hoverable editable\" {}>", data_attributes.string_view());
|
||||
builder.append(text);
|
||||
builder.append("</span>"sv);
|
||||
}
|
||||
|
@ -338,7 +358,7 @@ String InspectorClient::generate_dom_tree(JsonObject const& dom_tree)
|
|||
auto comment = node.get_deprecated_string("data"sv).release_value();
|
||||
comment = escape_html_entities(comment);
|
||||
|
||||
builder.appendff("<span class=\"hoverable comment\" {}>", data_attributes.string_view());
|
||||
builder.appendff("<span data-node-type=\"comment\" class=\"hoverable editable comment\" {}>", data_attributes.string_view());
|
||||
builder.appendff("<!--{}-->", comment);
|
||||
builder.append("</span>"sv);
|
||||
return;
|
||||
|
@ -365,14 +385,16 @@ String InspectorClient::generate_dom_tree(JsonObject const& dom_tree)
|
|||
|
||||
builder.appendff("<span class=\"hoverable\" {}>", data_attributes.string_view());
|
||||
builder.append("<span><</span>"sv);
|
||||
builder.appendff("<span class=\"tag\">{}</span>", name.to_lowercase());
|
||||
builder.appendff("<span data-node-type=\"tag\" class=\"editable tag\">{}</span>", name.to_lowercase());
|
||||
|
||||
if (auto attributes = node.get_object("attributes"sv); attributes.has_value()) {
|
||||
attributes->for_each_member([&builder](auto const& name, auto const& value) {
|
||||
builder.append(" "sv);
|
||||
builder.appendff("<span data-node-type=\"attribute\" data-attribute-name=\"{}\" class=\"editable\">", name);
|
||||
builder.appendff("<span class=\"attribute-name\">{}</span>", name);
|
||||
builder.append('=');
|
||||
builder.appendff("<span class=\"attribute-value\">\"{}\"</span>", value);
|
||||
builder.append("</span>"sv);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue