mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-29 06:02:07 +03:00
LibWebView: Add Inspector actions to be used as context menu callbacks
These allow for triggering an edit of a DOM node (as an alternative to double-clicking), removing a DOM node, and adding/removing DOM node attributes.
This commit is contained in:
parent
9a5fe740c6
commit
0ddc2ea8c4
Notes:
sideshowbarker
2024-07-17 09:49:33 +09:00
Author: https://github.com/trflynn89 Commit: https://github.com/SerenityOS/serenity/commit/0ddc2ea8c4 Pull-request: https://github.com/SerenityOS/serenity/pull/22182
@ -136,6 +136,28 @@ inspector.clearInspectedDOMNode = () => {
|
||||
}
|
||||
};
|
||||
|
||||
inspector.editDOMNodeID = nodeID => {
|
||||
if (pendingEditDOMNode === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
inspector.inspectDOMNodeID(nodeID);
|
||||
editDOMNode(pendingEditDOMNode);
|
||||
|
||||
pendingEditDOMNode = null;
|
||||
};
|
||||
|
||||
inspector.addAttributeToDOMNodeID = nodeID => {
|
||||
if (pendingEditDOMNode === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
inspector.inspectDOMNodeID(nodeID);
|
||||
addAttributeToDOMNode(pendingEditDOMNode);
|
||||
|
||||
pendingEditDOMNode = null;
|
||||
};
|
||||
|
||||
inspector.createPropertyTables = (computedStyle, resolvedStyle, customProperties) => {
|
||||
const createPropertyTable = (tableID, properties) => {
|
||||
let oldTable = document.getElementById(tableID);
|
||||
@ -176,62 +198,110 @@ 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;
|
||||
|
||||
const createDOMEditor = (onHandleChange, onCancelChange) => {
|
||||
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
|
||||
);
|
||||
try {
|
||||
onHandleChange(input.value);
|
||||
} catch {
|
||||
cancelChange();
|
||||
}
|
||||
};
|
||||
|
||||
const cancelChange = () => {
|
||||
input.removeEventListener("change", handleChange);
|
||||
input.removeEventListener("blur", cancelChange);
|
||||
|
||||
selectedDOMNode.classList.add("selected");
|
||||
input.parentNode.replaceChild(domNode, input);
|
||||
onCancelChange(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();
|
||||
});
|
||||
|
||||
return input;
|
||||
};
|
||||
|
||||
const parseDOMAttributes = value => {
|
||||
let element = document.createElement("div");
|
||||
element.innerHTML = `<div ${value}></div>`;
|
||||
|
||||
return element.children[0].attributes;
|
||||
};
|
||||
|
||||
const editDOMNode = domNode => {
|
||||
if (selectedDOMNode === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const domNodeID = selectedDOMNode.dataset.id;
|
||||
|
||||
const handleChange = value => {
|
||||
const type = domNode.dataset.nodeType;
|
||||
|
||||
if (type === "text" || type === "comment") {
|
||||
inspector.setDOMNodeText(domNodeID, value);
|
||||
} else if (type === "tag") {
|
||||
const element = document.createElement(value);
|
||||
inspector.setDOMNodeTag(domNodeID, value);
|
||||
} else if (type === "attribute") {
|
||||
const attributes = parseDOMAttributes(value);
|
||||
inspector.replaceDOMNodeAttribute(domNodeID, domNode.dataset.attributeName, attributes);
|
||||
}
|
||||
};
|
||||
|
||||
const cancelChange = editor => {
|
||||
editor.parentNode.replaceChild(domNode, editor);
|
||||
};
|
||||
|
||||
let editor = createDOMEditor(handleChange, cancelChange);
|
||||
editor.value = domNode.innerText;
|
||||
|
||||
domNode.parentNode.replaceChild(editor, domNode);
|
||||
};
|
||||
|
||||
const addAttributeToDOMNode = domNode => {
|
||||
if (selectedDOMNode === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const domNodeID = selectedDOMNode.dataset.id;
|
||||
|
||||
const handleChange = value => {
|
||||
const attributes = parseDOMAttributes(value);
|
||||
inspector.addDOMNodeAttributes(domNodeID, attributes);
|
||||
};
|
||||
|
||||
const cancelChange = () => {
|
||||
container.remove();
|
||||
};
|
||||
|
||||
let editor = createDOMEditor(handleChange, cancelChange);
|
||||
editor.placeholder = 'name="value"';
|
||||
|
||||
let nbsp = document.createElement("span");
|
||||
nbsp.innerHTML = " ";
|
||||
|
||||
let container = document.createElement("span");
|
||||
container.appendChild(nbsp);
|
||||
container.appendChild(editor);
|
||||
|
||||
domNode.parentNode.insertBefore(container, domNode.parentNode.lastChild);
|
||||
};
|
||||
|
||||
const requestContextMenu = (clientX, clientY, domNode) => {
|
||||
|
@ -141,6 +141,13 @@ InspectorClient::InspectorClient(ViewImplementation& content_web_view, ViewImple
|
||||
inspect();
|
||||
};
|
||||
|
||||
m_inspector_web_view.on_inspector_added_dom_node_attributes = [this](auto node_id, auto const& attributes) {
|
||||
m_content_web_view.add_dom_node_attributes(node_id, attributes);
|
||||
|
||||
m_pending_selection = node_id;
|
||||
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);
|
||||
|
||||
@ -216,6 +223,55 @@ void InspectorClient::select_node(i32 node_id)
|
||||
m_inspector_web_view.run_javascript(script);
|
||||
}
|
||||
|
||||
void InspectorClient::context_menu_edit_dom_node()
|
||||
{
|
||||
VERIFY(m_context_menu_dom_node_id.has_value());
|
||||
|
||||
auto script = MUST(String::formatted("inspector.editDOMNodeID({});", *m_context_menu_dom_node_id));
|
||||
m_inspector_web_view.run_javascript(script);
|
||||
|
||||
m_context_menu_dom_node_id.clear();
|
||||
m_context_menu_tag_or_attribute_name.clear();
|
||||
}
|
||||
|
||||
void InspectorClient::context_menu_remove_dom_node()
|
||||
{
|
||||
VERIFY(m_context_menu_dom_node_id.has_value());
|
||||
|
||||
m_content_web_view.remove_dom_node(*m_context_menu_dom_node_id);
|
||||
|
||||
m_pending_selection = m_body_node_id;
|
||||
inspect();
|
||||
|
||||
m_context_menu_dom_node_id.clear();
|
||||
m_context_menu_tag_or_attribute_name.clear();
|
||||
}
|
||||
|
||||
void InspectorClient::context_menu_add_dom_node_attribute()
|
||||
{
|
||||
VERIFY(m_context_menu_dom_node_id.has_value());
|
||||
|
||||
auto script = MUST(String::formatted("inspector.addAttributeToDOMNodeID({});", *m_context_menu_dom_node_id));
|
||||
m_inspector_web_view.run_javascript(script);
|
||||
|
||||
m_context_menu_dom_node_id.clear();
|
||||
m_context_menu_tag_or_attribute_name.clear();
|
||||
}
|
||||
|
||||
void InspectorClient::context_menu_remove_dom_node_attribute()
|
||||
{
|
||||
VERIFY(m_context_menu_dom_node_id.has_value());
|
||||
VERIFY(m_context_menu_tag_or_attribute_name.has_value());
|
||||
|
||||
m_content_web_view.replace_dom_node_attribute(*m_context_menu_dom_node_id, *m_context_menu_tag_or_attribute_name, {});
|
||||
|
||||
m_pending_selection = m_context_menu_dom_node_id;
|
||||
inspect();
|
||||
|
||||
m_context_menu_dom_node_id.clear();
|
||||
m_context_menu_tag_or_attribute_name.clear();
|
||||
}
|
||||
|
||||
void InspectorClient::load_inspector()
|
||||
{
|
||||
StringBuilder builder;
|
||||
|
@ -26,6 +26,11 @@ public:
|
||||
void select_default_node();
|
||||
void clear_selection();
|
||||
|
||||
void context_menu_edit_dom_node();
|
||||
void context_menu_remove_dom_node();
|
||||
void context_menu_add_dom_node_attribute();
|
||||
void context_menu_remove_dom_node_attribute();
|
||||
|
||||
Function<void(Gfx::IntPoint)> on_requested_dom_node_text_context_menu;
|
||||
Function<void(Gfx::IntPoint, String const&)> on_requested_dom_node_tag_context_menu;
|
||||
Function<void(Gfx::IntPoint, String const&)> on_requested_dom_node_attribute_context_menu;
|
||||
|
Loading…
Reference in New Issue
Block a user